forked from mirrors/gotosocial
Compare commits
10 commits
cc69250bbe
...
b3194f1fee
Author | SHA1 | Date | |
---|---|---|---|
b3194f1fee | |||
af29c06290 | |||
|
0ddc2edf19 | ||
|
3920bc87d1 | ||
|
4b05dcde43 | ||
|
9df4d38c43 | ||
|
be3718f6e4 | ||
|
517829ae6a | ||
|
0f812746b7 | ||
|
303a6a6b1d |
60 changed files with 1563 additions and 1171 deletions
10
README.md
10
README.md
|
@ -91,13 +91,13 @@ For a detailed view on what's implemented and what's not, and progress made towa
|
|||
|
||||
The Mastodon API has become the de facto standard for client communication with federated servers, so GoToSocial has implemented and extended the API with custom functionality.
|
||||
|
||||
In short, this means full support for modern, beautiful apps like [Tusky](https://tusky.app/) and [Semaphore](https://semaphore.social/).
|
||||
Though most apps that implement the Mastodon API should work, GoToSocial works reliably with beautiful apps like:
|
||||
|
||||
Tusky | Semaphore
|
||||
:-----------------------------------------------------------:|:------------------------------------------------------------------:
|
||||
![An image of GoToSocial in Tusky](./docs/assets/tusky.png) | ![An image of GoToSocial in Semaphore](./docs/assets/semaphore.png)
|
||||
* [Tusky](https://tusky.app/) for Android
|
||||
* [Semaphore](https://semaphore.social/) in the browser
|
||||
* [Feditext](https://fedi.software/@Feditext) (beta) on iOS, iPadOS and macOS
|
||||
|
||||
If you're used to using Mastodon with Tusky or Semaphore, you'll find using GoToSocial a breeze.
|
||||
If you've used Mastodon with any of these apps before, you'll find using GoToSocial a breeze.
|
||||
|
||||
### Granular post settings
|
||||
|
||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 673 KiB |
Binary file not shown.
Before Width: | Height: | Size: 624 KiB |
|
@ -2,7 +2,7 @@
|
|||
|
||||
## Where's the user interface?
|
||||
|
||||
GoToSocial is just a bare server for the most part and is designed to be used thru external applications. [Semaphore](https://semaphore.social/) and [Tusky](https://tusky.app/) are the best-supported, but anything that supports the Mastodon API should work, other than the features GoToSocial doesn't yet have. Permalinks and profile pages are served directly through GoToSocial as well as the settings panel, but most interaction goes through the apps.
|
||||
GoToSocial is just a bare server for the most part and is designed to be used thru external applications. [Semaphore](https://semaphore.social/) in the browser, [Tusky](https://tusky.app/) for Android and [Feditext](https://fedi.software/@Feditext) for iOS, iPadOS and macOS are the best-supported. Anything that supports the Mastodon API should work, other than the features GoToSocial doesn't yet have. Permalinks and profile pages are served directly through GoToSocial as well as the settings panel, but most interaction goes through the apps.
|
||||
|
||||
## Why aren't my posts showing up on my profile page?
|
||||
|
||||
|
|
|
@ -78,7 +78,7 @@ You can use the GoToSocial binary to also create and promote your user account.
|
|||
|
||||
## Login
|
||||
|
||||
You should now be able to log in to your instance using the email address and password of the account you just created. We recommend using [Semaphore](https://semaphore.social) or [Tusky](https://tusky.app) for this.
|
||||
You should now be able to log in to your instance using the email address and password of the account you just created.
|
||||
|
||||
## (Optional) Enable the systemd service
|
||||
|
||||
|
|
|
@ -38,7 +38,7 @@ Since GoToSocial is still in alpha, there are plenty of bugs. We use [GitHub iss
|
|||
|
||||
### Client App Issues
|
||||
|
||||
GoToSocial works great with Tusky and Semaphore, but some other client applications still need work or have issues connecting to GoToSocial. We're tracking them [right here](https://github.com/superseriousbusiness/gotosocial/projects/5). It's our goal to make any app that's compatible with the Mastodon API work seamlessly with GoToSocial.
|
||||
GoToSocial works great with Tusky, Semaphore and Feditext, but some other client applications still need work or have issues connecting to GoToSocial. We're tracking them [right here](https://github.com/superseriousbusiness/gotosocial/projects/5). It's our goal to make any app that's compatible with the Mastodon API work seamlessly with GoToSocial.
|
||||
|
||||
### Federation Issues
|
||||
|
||||
|
|
8
go.mod
8
go.mod
|
@ -45,7 +45,7 @@ require (
|
|||
github.com/superseriousbusiness/activity v1.4.0-gts
|
||||
github.com/superseriousbusiness/exif-terminator v0.5.0
|
||||
github.com/superseriousbusiness/oauth2/v4 v4.3.2-SSB.0.20230227143000-f4900831d6c8
|
||||
github.com/tdewolff/minify/v2 v2.12.7
|
||||
github.com/tdewolff/minify/v2 v2.12.8
|
||||
github.com/ulule/limiter/v3 v3.11.2
|
||||
github.com/uptrace/bun v1.1.14
|
||||
github.com/uptrace/bun/dialect/pgdialect v1.1.14
|
||||
|
@ -62,8 +62,8 @@ require (
|
|||
golang.org/x/crypto v0.12.0
|
||||
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1
|
||||
golang.org/x/image v0.11.0
|
||||
golang.org/x/net v0.12.0
|
||||
golang.org/x/oauth2 v0.10.0
|
||||
golang.org/x/net v0.14.0
|
||||
golang.org/x/oauth2 v0.11.0
|
||||
golang.org/x/text v0.12.0
|
||||
gopkg.in/mcuadros/go-syslog.v2 v2.3.0
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
|
@ -152,7 +152,7 @@ require (
|
|||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
github.com/subosito/gotenv v1.4.2 // indirect
|
||||
github.com/superseriousbusiness/go-jpeg-image-structure/v2 v2.0.0-20220321154430-d89a106fdabe // indirect
|
||||
github.com/tdewolff/parse/v2 v2.6.6 // indirect
|
||||
github.com/tdewolff/parse/v2 v2.6.7 // indirect
|
||||
github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc // indirect
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||
github.com/ugorji/go/codec v1.2.11 // indirect
|
||||
|
|
20
go.sum
20
go.sum
|
@ -111,7 +111,6 @@ github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyY
|
|||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
|
||||
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/cheekybits/is v0.0.0-20150225183255-68e9c0620927/go.mod h1:h/aW8ynjgkuj+NQRlZcDbAbM1ORAbXjXX77sX7T289U=
|
||||
github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY=
|
||||
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams=
|
||||
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk=
|
||||
|
@ -149,7 +148,6 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
|
|||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/disintegration/imaging v1.6.2 h1:w1LecBlG2Lnp8B3jk5zSuNqd7b4DXhcjwek1ei82L+c=
|
||||
github.com/disintegration/imaging v1.6.2/go.mod h1:44/5580QXChDfwIclfc/PCwrr44amcmDAg8hxG0Ewe4=
|
||||
github.com/djherbis/atime v1.1.0/go.mod h1:28OF6Y8s3NQWwacXc5eZTsEsiMzp7LF8MbXE+XJPdBE=
|
||||
github.com/docker/go-units v0.4.0 h1:3uh0PgVws3nIA0Q+MwDC8yjEPf9zjRfZZWXZYDct3Tw=
|
||||
github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
|
||||
github.com/dsoprea/go-exif/v2 v2.0.0-20200321225314-640175a69fe4/go.mod h1:Lm2lMM2zx8p4a34ZemkaUV95AnMl4ZvLbCUbwOvLC2E=
|
||||
|
@ -438,7 +436,6 @@ github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
|||
github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||
github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
|
||||
github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
|
||||
github.com/matryer/try v0.0.0-20161228173917-9ac251b645a2/go.mod h1:0KeJpeMD6o+O4hW7qJOT7vyQPKrWmj26uf5wMc/IiIs=
|
||||
github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ=
|
||||
github.com/mattn/go-colorable v0.1.7/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||
github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||
|
@ -564,11 +561,10 @@ github.com/superseriousbusiness/go-jpeg-image-structure/v2 v2.0.0-20220321154430
|
|||
github.com/superseriousbusiness/go-jpeg-image-structure/v2 v2.0.0-20220321154430-d89a106fdabe/go.mod h1:gH4P6gN1V+wmIw5o97KGaa1RgXB/tVpC2UNzijhg3E4=
|
||||
github.com/superseriousbusiness/oauth2/v4 v4.3.2-SSB.0.20230227143000-f4900831d6c8 h1:nTIhuP157oOFcscuoK1kCme1xTeGIzztSw70lX9NrDQ=
|
||||
github.com/superseriousbusiness/oauth2/v4 v4.3.2-SSB.0.20230227143000-f4900831d6c8/go.mod h1:uYC/W92oVRJ49Vh1GcvTqpeFqHi+Ovrl2sMllQWRAEo=
|
||||
github.com/tdewolff/minify/v2 v2.12.7 h1:pBzz2tAfz5VghOXiQIsSta6srhmTeinQPjRDHWoumCA=
|
||||
github.com/tdewolff/minify/v2 v2.12.7/go.mod h1:ZRKTheiOGyLSK8hOZWWv+YoJAECzDivNgAlVYDHp/Ws=
|
||||
github.com/tdewolff/parse/v2 v2.6.6 h1:Yld+0CrKUJaCV78DL1G2nk3C9lKrxyRTux5aaK/AkDo=
|
||||
github.com/tdewolff/parse/v2 v2.6.6/go.mod h1:woz0cgbLwFdtbjJu8PIKxhW05KplTFQkOdX78o+Jgrs=
|
||||
github.com/tdewolff/test v1.0.7/go.mod h1:6DAvZliBAAnD7rhVgwaM7DE5/d9NMOAJ09SqYqeK4QE=
|
||||
github.com/tdewolff/minify/v2 v2.12.8 h1:Q2BqOTmlMjoutkuD/OPCnJUpIqrzT3nRPkw+q+KpXS0=
|
||||
github.com/tdewolff/minify/v2 v2.12.8/go.mod h1:YRgk7CC21LZnbuke2fmYnCTq+zhCgpb0yJACOTUNJ1E=
|
||||
github.com/tdewolff/parse/v2 v2.6.7 h1:WrFllrqmzAcrKHzoYgMupqgUBIfBVOb0yscFzDf8bBg=
|
||||
github.com/tdewolff/parse/v2 v2.6.7/go.mod h1:XHDhaU6IBgsryfdnpzUXBlT6leW/l25yrFBTEb4eIyM=
|
||||
github.com/tdewolff/test v1.0.9 h1:SswqJCmeN4B+9gEAi/5uqT0qpi1y2/2O47V/1hhGZT0=
|
||||
github.com/tdewolff/test v1.0.9/go.mod h1:6DAvZliBAAnD7rhVgwaM7DE5/d9NMOAJ09SqYqeK4QE=
|
||||
github.com/tidwall/btree v0.0.0-20191029221954-400434d76274 h1:G6Z6HvJuPjG6XfNGi/feOATzeJrfgTNJY+rGrHbA04E=
|
||||
|
@ -779,8 +775,8 @@ golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96b
|
|||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.12.0 h1:cfawfvKITfUsFCeJIHJrbSxpeu/E81khclypR0GVT50=
|
||||
golang.org/x/net v0.12.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA=
|
||||
golang.org/x/net v0.14.0 h1:BONx9s002vGdD9umnlX1Po8vOZmrgH34qlHcD1MfK14=
|
||||
golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
|
@ -792,8 +788,8 @@ golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ
|
|||
golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.10.0 h1:zHCpF2Khkwy4mMB4bv0U37YtJdTGW8jI0glAApi0Kh8=
|
||||
golang.org/x/oauth2 v0.10.0/go.mod h1:kTpgurOux7LqtuxjuyZa4Gj2gdezIt/jQtGnNFfypQI=
|
||||
golang.org/x/oauth2 v0.11.0 h1:vPL4xzxBM4niKCW6g9whtaWVXTJf1U5e4aZxxFx/gbU=
|
||||
golang.org/x/oauth2 v0.11.0/go.mod h1:LdF7O/8bLR/qWK9DrpXmbHLTouvRHK0SgJl0GmDBchk=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
|
|
|
@ -11,7 +11,7 @@ import (
|
|||
"github.com/stretchr/testify/suite"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/api/auth"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||
"github.com/superseriousbusiness/gotosocial/testrig"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/util"
|
||||
)
|
||||
|
||||
type AuthAuthorizeTestSuite struct {
|
||||
|
@ -51,8 +51,8 @@ func (suite *AuthAuthorizeTestSuite) TestAccountAuthorizeHandler() {
|
|||
mutateUserAccount: func(user *gtsmodel.User, account *gtsmodel.Account) []string {
|
||||
user.ConfirmedAt = time.Now()
|
||||
user.Email = user.UnconfirmedEmail
|
||||
user.Approved = testrig.TrueBool()
|
||||
user.Disabled = testrig.TrueBool()
|
||||
user.Approved = util.Ptr(true)
|
||||
user.Disabled = util.Ptr(true)
|
||||
return []string{"confirmed_at", "email", "approved", "disabled"}
|
||||
},
|
||||
expectedStatusCode: http.StatusSeeOther,
|
||||
|
@ -63,8 +63,8 @@ func (suite *AuthAuthorizeTestSuite) TestAccountAuthorizeHandler() {
|
|||
mutateUserAccount: func(user *gtsmodel.User, account *gtsmodel.Account) []string {
|
||||
user.ConfirmedAt = time.Now()
|
||||
user.Email = user.UnconfirmedEmail
|
||||
user.Approved = testrig.TrueBool()
|
||||
user.Disabled = testrig.FalseBool()
|
||||
user.Approved = util.Ptr(true)
|
||||
user.Disabled = util.Ptr(false)
|
||||
account.SuspendedAt = time.Now()
|
||||
return []string{"confirmed_at", "email", "approved", "disabled"}
|
||||
},
|
||||
|
|
|
@ -87,7 +87,7 @@ func (m *Module) AccountCreatePOSTHandler(c *gin.Context) {
|
|||
return
|
||||
}
|
||||
|
||||
if err := validateCreateAccount(form); err != nil {
|
||||
if err := validateNormalizeCreateAccount(form); err != nil {
|
||||
apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGetV1)
|
||||
return
|
||||
}
|
||||
|
@ -110,9 +110,10 @@ func (m *Module) AccountCreatePOSTHandler(c *gin.Context) {
|
|||
c.JSON(http.StatusOK, ti)
|
||||
}
|
||||
|
||||
// validateCreateAccount checks through all the necessary prerequisites for creating a new account,
|
||||
// validateNormalizeCreateAccount checks through all the necessary prerequisites for creating a new account,
|
||||
// according to the provided account create request. If the account isn't eligible, an error will be returned.
|
||||
func validateCreateAccount(form *apimodel.AccountCreateRequest) error {
|
||||
// Side effect: normalizes the provided language tag for the user's locale.
|
||||
func validateNormalizeCreateAccount(form *apimodel.AccountCreateRequest) error {
|
||||
if form == nil {
|
||||
return errors.New("form was nil")
|
||||
}
|
||||
|
@ -137,9 +138,11 @@ func validateCreateAccount(form *apimodel.AccountCreateRequest) error {
|
|||
return errors.New("agreement to terms and conditions not given")
|
||||
}
|
||||
|
||||
if err := validate.Language(form.Locale); err != nil {
|
||||
locale, err := validate.Language(form.Locale)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
form.Locale = locale
|
||||
|
||||
return validate.SignUpReason(form.Reason, config.GetAccountsReasonRequired())
|
||||
}
|
||||
|
|
|
@ -138,7 +138,7 @@ func (suite *ReportResolveTestSuite) TestReportResolve2() {
|
|||
testToken := suite.testTokens["admin_account"]
|
||||
testUser := suite.testUsers["admin_account"]
|
||||
testReportID := suite.testReports["local_account_2_report_remote_account_1"].ID
|
||||
var actionTakenComment *string = testrig.StringPtr("no action was taken, this is a frivolous report you boob")
|
||||
var actionTakenComment *string = util.Ptr("no action was taken, this is a frivolous report you boob")
|
||||
|
||||
report, err := suite.resolveReport(testAccount, testToken, testUser, testReportID, http.StatusOK, "", actionTakenComment)
|
||||
suite.NoError(err)
|
||||
|
|
|
@ -32,6 +32,7 @@ import (
|
|||
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/oauth"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/util"
|
||||
"github.com/superseriousbusiness/gotosocial/testrig"
|
||||
)
|
||||
|
||||
|
@ -963,7 +964,7 @@ func (suite *ReportsGetTestSuite) TestReportsGetResolvedTargetAccount() {
|
|||
testAccount := suite.testAccounts["admin_account"]
|
||||
testToken := suite.testTokens["admin_account"]
|
||||
testUser := suite.testUsers["admin_account"]
|
||||
resolved := testrig.FalseBool()
|
||||
resolved := util.Ptr(false)
|
||||
targetAccount := suite.testAccounts["local_account_2"]
|
||||
|
||||
reports, link, err := suite.getReports(testAccount, testToken, testUser, http.StatusOK, "", resolved, "", targetAccount.ID, "", "", "", 20)
|
||||
|
|
|
@ -32,6 +32,7 @@ import (
|
|||
"github.com/superseriousbusiness/gotosocial/internal/api/client/instance"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/config"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/util"
|
||||
"github.com/superseriousbusiness/gotosocial/testrig"
|
||||
)
|
||||
|
||||
|
@ -231,7 +232,7 @@ func (suite *InstancePeersGetTestSuite) TestInstancePeersGetAllWithObfuscated()
|
|||
Domain: "omg.just.the.worst.org.ever",
|
||||
CreatedByAccountID: "01F8MH17FWEB39HZJ76B6VXSKF",
|
||||
PublicComment: "just absolutely the worst, wowza",
|
||||
Obfuscate: testrig.TrueBool(),
|
||||
Obfuscate: util.Ptr(true),
|
||||
})
|
||||
suite.NoError(err)
|
||||
|
||||
|
|
|
@ -32,6 +32,7 @@ import (
|
|||
"github.com/superseriousbusiness/gotosocial/internal/config"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/oauth"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/util"
|
||||
"github.com/superseriousbusiness/gotosocial/testrig"
|
||||
)
|
||||
|
||||
|
@ -197,7 +198,7 @@ func (suite *ReportsGetTestSuite) TestGetReports4() {
|
|||
testAccount := suite.testAccounts["local_account_2"]
|
||||
testToken := suite.testTokens["local_account_2"]
|
||||
testUser := suite.testUsers["local_account_2"]
|
||||
resolved := testrig.FalseBool()
|
||||
resolved := util.Ptr(false)
|
||||
|
||||
reports, link, err := suite.getReports(testAccount, testToken, testUser, http.StatusOK, resolved, "", "", "", "", 20)
|
||||
suite.NoError(err)
|
||||
|
@ -252,7 +253,7 @@ func (suite *ReportsGetTestSuite) TestGetReports5() {
|
|||
testAccount := suite.testAccounts["local_account_1"]
|
||||
testToken := suite.testTokens["local_account_1"]
|
||||
testUser := suite.testUsers["local_account_1"]
|
||||
resolved := testrig.TrueBool()
|
||||
resolved := util.Ptr(true)
|
||||
|
||||
reports, link, err := suite.getReports(testAccount, testToken, testUser, http.StatusOK, resolved, "", "", "", "", 20)
|
||||
suite.NoError(err)
|
||||
|
@ -323,7 +324,7 @@ func (suite *ReportsGetTestSuite) TestGetReports7() {
|
|||
testAccount := suite.testAccounts["local_account_2"]
|
||||
testToken := suite.testTokens["local_account_2"]
|
||||
testUser := suite.testUsers["local_account_2"]
|
||||
resolved := testrig.FalseBool()
|
||||
resolved := util.Ptr(false)
|
||||
|
||||
reports, link, err := suite.getReports(testAccount, testToken, testUser, http.StatusOK, resolved, "01F8MH5ZK5VRH73AKHQM6Y9VNX", "", "", "", 20)
|
||||
suite.NoError(err)
|
||||
|
|
|
@ -98,7 +98,7 @@ func (m *Module) StatusCreatePOSTHandler(c *gin.Context) {
|
|||
// }
|
||||
// form.Status += "\n\nsent from " + user + "'s iphone\n"
|
||||
|
||||
if err := validateCreateStatus(form); err != nil {
|
||||
if err := validateNormalizeCreateStatus(form); err != nil {
|
||||
apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGetV1)
|
||||
return
|
||||
}
|
||||
|
@ -112,7 +112,9 @@ func (m *Module) StatusCreatePOSTHandler(c *gin.Context) {
|
|||
c.JSON(http.StatusOK, apiStatus)
|
||||
}
|
||||
|
||||
func validateCreateStatus(form *apimodel.AdvancedStatusCreateForm) error {
|
||||
// validateNormalizeCreateStatus checks the form for disallowed combinations of attachments and overlength inputs.
|
||||
// Side effect: normalizes the post's language tag.
|
||||
func validateNormalizeCreateStatus(form *apimodel.AdvancedStatusCreateForm) error {
|
||||
hasStatus := form.Status != ""
|
||||
hasMedia := len(form.MediaIDs) != 0
|
||||
hasPoll := form.Poll != nil
|
||||
|
@ -162,9 +164,11 @@ func validateCreateStatus(form *apimodel.AdvancedStatusCreateForm) error {
|
|||
}
|
||||
|
||||
if form.Language != "" {
|
||||
if err := validate.Language(form.Language); err != nil {
|
||||
language, err := validate.Language(form.Language)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
form.Language = language
|
||||
}
|
||||
|
||||
return nil
|
||||
|
|
|
@ -391,6 +391,42 @@ func (suite *StatusCreateTestSuite) TestAttachNewMediaSuccess() {
|
|||
suite.Equal(statusResponse.ID, gtsAttachment.StatusID)
|
||||
}
|
||||
|
||||
// Post a new status with a language tag that is not in canonical format
|
||||
func (suite *StatusCreateTestSuite) TestPostNewStatusWithNoncanonicalLanguageTag() {
|
||||
t := suite.testTokens["local_account_1"]
|
||||
oauthToken := oauth.DBTokenToToken(t)
|
||||
|
||||
// setup
|
||||
recorder := httptest.NewRecorder()
|
||||
ctx, _ := testrig.CreateGinTestContext(recorder, nil)
|
||||
ctx.Set(oauth.SessionAuthorizedApplication, suite.testApplications["application_1"])
|
||||
ctx.Set(oauth.SessionAuthorizedToken, oauthToken)
|
||||
ctx.Set(oauth.SessionAuthorizedUser, suite.testUsers["local_account_1"])
|
||||
ctx.Set(oauth.SessionAuthorizedAccount, suite.testAccounts["local_account_1"])
|
||||
ctx.Request = httptest.NewRequest(http.MethodPost, fmt.Sprintf("http://localhost:8080/%s", statuses.BasePath), nil) // the endpoint we're hitting
|
||||
ctx.Request.Header.Set("accept", "application/json")
|
||||
ctx.Request.Form = url.Values{
|
||||
"status": {"English? what's English? i speak American"},
|
||||
"language": {"en-us"},
|
||||
}
|
||||
suite.statusModule.StatusCreatePOSTHandler(ctx)
|
||||
|
||||
suite.EqualValues(http.StatusOK, recorder.Code)
|
||||
|
||||
result := recorder.Result()
|
||||
defer result.Body.Close()
|
||||
b, err := ioutil.ReadAll(result.Body)
|
||||
suite.NoError(err)
|
||||
|
||||
statusReply := &apimodel.Status{}
|
||||
err = json.Unmarshal(b, statusReply)
|
||||
suite.NoError(err)
|
||||
|
||||
suite.Equal("<p>English? what's English? i speak American</p>", statusReply.Content)
|
||||
suite.NotNil(statusReply.Language)
|
||||
suite.Equal("en-US", *statusReply.Language)
|
||||
}
|
||||
|
||||
func TestStatusCreateTestSuite(t *testing.T) {
|
||||
suite.Run(t, new(StatusCreateTestSuite))
|
||||
}
|
||||
|
|
|
@ -36,6 +36,7 @@ import (
|
|||
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/id"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/oauth"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/util"
|
||||
"github.com/superseriousbusiness/gotosocial/testrig"
|
||||
)
|
||||
|
||||
|
@ -165,14 +166,14 @@ func (suite *StatusPinTestSuite) TestPinStatusTooManyPins() {
|
|||
PinnedAt: time.Now(),
|
||||
URL: "stub " + strconv.Itoa(i),
|
||||
URI: "stub " + strconv.Itoa(i),
|
||||
Local: testrig.TrueBool(),
|
||||
Local: util.Ptr(true),
|
||||
AccountID: testAccount.ID,
|
||||
AccountURI: testAccount.URI,
|
||||
Visibility: gtsmodel.VisibilityPublic,
|
||||
Federated: testrig.TrueBool(),
|
||||
Boostable: testrig.TrueBool(),
|
||||
Replyable: testrig.TrueBool(),
|
||||
Likeable: testrig.TrueBool(),
|
||||
Federated: util.Ptr(true),
|
||||
Boostable: util.Ptr(true),
|
||||
Replyable: util.Ptr(true),
|
||||
Likeable: util.Ptr(true),
|
||||
ActivityStreamsType: ap.ObjectNote,
|
||||
}
|
||||
if err := suite.db.PutStatus(ctx, status); err != nil {
|
||||
|
|
|
@ -36,6 +36,7 @@ import (
|
|||
"github.com/superseriousbusiness/gotosocial/internal/log"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/state"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/uris"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/util"
|
||||
"github.com/uptrace/bun"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
)
|
||||
|
@ -198,17 +199,15 @@ func (a *adminDB) NewSignup(ctx context.Context, newSignup gtsmodel.NewSignup) (
|
|||
user.Email = newSignup.Email
|
||||
}
|
||||
|
||||
trueBool := func() *bool { t := true; return &t }
|
||||
|
||||
if newSignup.Admin {
|
||||
// Make new user mod + admin.
|
||||
user.Moderator = trueBool()
|
||||
user.Admin = trueBool()
|
||||
user.Moderator = util.Ptr(true)
|
||||
user.Admin = util.Ptr(true)
|
||||
}
|
||||
|
||||
if newSignup.PreApproved {
|
||||
// Mark new user as approved.
|
||||
user.Approved = trueBool()
|
||||
user.Approved = util.Ptr(true)
|
||||
}
|
||||
|
||||
// Insert the user!
|
||||
|
|
|
@ -25,7 +25,7 @@ import (
|
|||
"github.com/superseriousbusiness/gotosocial/internal/config"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/db"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||
"github.com/superseriousbusiness/gotosocial/testrig"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/util"
|
||||
)
|
||||
|
||||
type InstanceTestSuite struct {
|
||||
|
@ -103,7 +103,7 @@ func (suite *InstanceTestSuite) TestGetInstanceModeratorAddressesZorkAsModerator
|
|||
// Promote zork to moderator role.
|
||||
testUser := >smodel.User{}
|
||||
*testUser = *suite.testUsers["local_account_1"]
|
||||
testUser.Moderator = testrig.TrueBool()
|
||||
testUser.Moderator = util.Ptr(true)
|
||||
if err := suite.db.UpdateUser(context.Background(), testUser, "moderator"); err != nil {
|
||||
suite.FailNow(err.Error())
|
||||
}
|
||||
|
@ -117,8 +117,8 @@ func (suite *InstanceTestSuite) TestGetInstanceModeratorAddressesNoAdmin() {
|
|||
// Demote admin from admin + moderator roles.
|
||||
testUser := >smodel.User{}
|
||||
*testUser = *suite.testUsers["admin_account"]
|
||||
testUser.Admin = testrig.FalseBool()
|
||||
testUser.Moderator = testrig.FalseBool()
|
||||
testUser.Admin = util.Ptr(false)
|
||||
testUser.Moderator = util.Ptr(false)
|
||||
if err := suite.db.UpdateUser(context.Background(), testUser, "admin", "moderator"); err != nil {
|
||||
suite.FailNow(err.Error())
|
||||
}
|
||||
|
|
|
@ -28,7 +28,7 @@ import (
|
|||
"github.com/superseriousbusiness/gotosocial/internal/db"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/id"
|
||||
"github.com/superseriousbusiness/gotosocial/testrig"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/util"
|
||||
)
|
||||
|
||||
func (suite *NotificationTestSuite) spamNotifs() {
|
||||
|
@ -70,7 +70,7 @@ func (suite *NotificationTestSuite) spamNotifs() {
|
|||
TargetAccountID: targetAccountID,
|
||||
OriginAccountID: originAccountID,
|
||||
StatusID: statusID,
|
||||
Read: testrig.FalseBool(),
|
||||
Read: util.Ptr(false),
|
||||
}
|
||||
|
||||
if err := suite.db.Put(context.Background(), notif); err != nil {
|
||||
|
|
|
@ -28,7 +28,7 @@ import (
|
|||
"github.com/superseriousbusiness/gotosocial/internal/db"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/id"
|
||||
"github.com/superseriousbusiness/gotosocial/testrig"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/util"
|
||||
)
|
||||
|
||||
type RelationshipTestSuite struct {
|
||||
|
@ -892,7 +892,7 @@ func (suite *RelationshipTestSuite) TestUpdateFollow() {
|
|||
follow := >smodel.Follow{}
|
||||
*follow = *suite.testFollows["local_account_1_admin_account"]
|
||||
|
||||
follow.Notify = testrig.TrueBool()
|
||||
follow.Notify = util.Ptr(true)
|
||||
if err := suite.db.UpdateFollow(ctx, follow, "notify"); err != nil {
|
||||
suite.FailNow(err.Error())
|
||||
}
|
||||
|
|
|
@ -24,6 +24,7 @@ import (
|
|||
"github.com/stretchr/testify/suite"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/db"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/util"
|
||||
"github.com/superseriousbusiness/gotosocial/testrig"
|
||||
)
|
||||
|
||||
|
@ -88,7 +89,7 @@ func (suite *ReportTestSuite) TestPutReport() {
|
|||
TargetAccountID: "01F8MH5ZK5VRH73AKHQM6Y9VNX",
|
||||
Comment: "another report",
|
||||
StatusIDs: []string{"01FVW7JHQFSFK166WWKR8CBA6M"},
|
||||
Forwarded: testrig.TrueBool(),
|
||||
Forwarded: util.Ptr(true),
|
||||
}
|
||||
|
||||
err := suite.db.PutReport(ctx, report)
|
||||
|
|
|
@ -26,7 +26,7 @@ import (
|
|||
"github.com/superseriousbusiness/gotosocial/internal/ap"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/id"
|
||||
"github.com/superseriousbusiness/gotosocial/testrig"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/util"
|
||||
)
|
||||
|
||||
type TimelineTestSuite struct {
|
||||
|
@ -52,20 +52,20 @@ func getFutureStatus() *gtsmodel.Status {
|
|||
EmojiIDs: []string{},
|
||||
CreatedAt: theDistantFuture,
|
||||
UpdatedAt: theDistantFuture,
|
||||
Local: testrig.TrueBool(),
|
||||
Local: util.Ptr(true),
|
||||
AccountURI: "http://localhost:8080/users/admin",
|
||||
AccountID: "01F8MH17FWEB39HZJ76B6VXSKF",
|
||||
InReplyToID: "",
|
||||
BoostOfID: "",
|
||||
ContentWarning: "",
|
||||
Visibility: gtsmodel.VisibilityPublic,
|
||||
Sensitive: testrig.FalseBool(),
|
||||
Sensitive: util.Ptr(false),
|
||||
Language: "en",
|
||||
CreatedWithApplicationID: "01F8MGXQRHYF5QPMTMXP78QC2F",
|
||||
Federated: testrig.TrueBool(),
|
||||
Boostable: testrig.TrueBool(),
|
||||
Replyable: testrig.TrueBool(),
|
||||
Likeable: testrig.TrueBool(),
|
||||
Federated: util.Ptr(true),
|
||||
Boostable: util.Ptr(true),
|
||||
Replyable: util.Ptr(true),
|
||||
Likeable: util.Ptr(true),
|
||||
ActivityStreamsType: ap.ObjectNote,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -28,6 +28,7 @@ import (
|
|||
"github.com/stretchr/testify/suite"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/federation"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/util"
|
||||
"github.com/superseriousbusiness/gotosocial/testrig"
|
||||
)
|
||||
|
||||
|
@ -80,9 +81,9 @@ func (suite *FederatingActorTestSuite) TestSendRemoteFollower() {
|
|||
UpdatedAt: testrig.TimeMustParse("2022-06-02T12:22:21+02:00"),
|
||||
AccountID: testRemoteAccount.ID,
|
||||
TargetAccountID: testAccount.ID,
|
||||
ShowReblogs: testrig.TrueBool(),
|
||||
ShowReblogs: util.Ptr(true),
|
||||
URI: "http://fossbros-anonymous.io/users/foss_satan/follows/01G1TRWV4AYCDBX5HRWT2EVBCV",
|
||||
Notify: testrig.FalseBool(),
|
||||
Notify: util.Ptr(false),
|
||||
})
|
||||
suite.NoError(err)
|
||||
|
||||
|
|
|
@ -21,13 +21,13 @@ import (
|
|||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"codeberg.org/gruf/go-kv"
|
||||
"codeberg.org/gruf/go-logger/v2/level"
|
||||
"github.com/superseriousbusiness/activity/pub"
|
||||
"github.com/superseriousbusiness/activity/streams/vocab"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/ap"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/db"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/id"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/log"
|
||||
|
@ -47,14 +47,16 @@ import (
|
|||
// Under certain conditions and network activities, Create may be called
|
||||
// multiple times for the same ActivityStreams object.
|
||||
func (f *federatingDB) Create(ctx context.Context, asType vocab.Type) error {
|
||||
if log.Level() >= level.DEBUG {
|
||||
if log.Level() >= level.TRACE {
|
||||
i, err := marshalItem(asType)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
l := log.WithContext(ctx).
|
||||
WithField("create", i)
|
||||
l.Trace("entering Create")
|
||||
|
||||
log.
|
||||
WithContext(ctx).
|
||||
WithField("create", i).
|
||||
Trace("entering Create")
|
||||
}
|
||||
|
||||
receivingAccount, requestingAccount, internal := extractFromCtx(ctx)
|
||||
|
@ -116,92 +118,125 @@ func (f *federatingDB) activityBlock(ctx context.Context, asType vocab.Type, rec
|
|||
CREATE HANDLERS
|
||||
*/
|
||||
|
||||
func (f *federatingDB) activityCreate(ctx context.Context, asType vocab.Type, receivingAccount *gtsmodel.Account, requestingAccount *gtsmodel.Account) error {
|
||||
// activityCreate handles asType Create by checking
|
||||
// the Object entries of the Create and calling other
|
||||
// handlers as appropriate.
|
||||
func (f *federatingDB) activityCreate(
|
||||
ctx context.Context,
|
||||
asType vocab.Type,
|
||||
receivingAccount *gtsmodel.Account,
|
||||
requestingAccount *gtsmodel.Account,
|
||||
) error {
|
||||
create, ok := asType.(vocab.ActivityStreamsCreate)
|
||||
if !ok {
|
||||
return errors.New("activityCreate: could not convert type to create")
|
||||
return gtserror.Newf("could not convert asType %T to ActivityStreamsCreate", asType)
|
||||
}
|
||||
|
||||
// create should have an object
|
||||
object := create.GetActivityStreamsObject()
|
||||
if object == nil {
|
||||
return errors.New("Create had no Object")
|
||||
// Create must have an Object.
|
||||
objectProp := create.GetActivityStreamsObject()
|
||||
if objectProp == nil {
|
||||
return gtserror.New("create had no Object")
|
||||
}
|
||||
|
||||
errs := []string{}
|
||||
// iterate through the object(s) to see what we're meant to be creating
|
||||
for objectIter := object.Begin(); objectIter != object.End(); objectIter = objectIter.Next() {
|
||||
asObjectType := objectIter.GetType()
|
||||
if asObjectType == nil {
|
||||
// currently we can't do anything with just a Create of something that's not an Object with a type
|
||||
// TODO: process a Create with an Object that's just a URI or something
|
||||
errs = append(errs, "object of Create was not a Type")
|
||||
// Iterate through the Object property and process FIRST provided statusable.
|
||||
// todo: https://github.com/superseriousbusiness/gotosocial/issues/1905
|
||||
for iter := objectProp.Begin(); iter != objectProp.End(); iter = iter.Next() {
|
||||
object := iter.GetType()
|
||||
if object == nil {
|
||||
// Can't do Create with Object that's just a URI.
|
||||
// Warn log this because it's an AP error.
|
||||
log.Warn(ctx, "object entry was not a type: %[1]T%[1]+v", iter)
|
||||
continue
|
||||
}
|
||||
|
||||
// we have a type -- what is it?
|
||||
asObjectTypeName := asObjectType.GetTypeName()
|
||||
switch asObjectTypeName {
|
||||
case ap.ObjectNote:
|
||||
// CREATE A NOTE
|
||||
if err := f.createNote(ctx, objectIter.GetActivityStreamsNote(), receivingAccount, requestingAccount); err != nil {
|
||||
errs = append(errs, err.Error())
|
||||
}
|
||||
default:
|
||||
errs = append(errs, fmt.Sprintf("received an object on a Create that we couldn't handle: %s", asObjectType.GetTypeName()))
|
||||
// Ensure given object type is a statusable.
|
||||
statusable, ok := object.(ap.Statusable)
|
||||
if !ok {
|
||||
// Can't (currently) Create anything other than a Statusable. ([1] is a format arg index)
|
||||
log.Debugf(ctx, "object entry type (currently) unsupported: %[1]T%[1]+v", object)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
if len(errs) != 0 {
|
||||
return fmt.Errorf("activityCreate: one or more errors while processing activity: %s", strings.Join(errs, "; "))
|
||||
// Handle creation of statusable.
|
||||
return f.createStatusable(ctx,
|
||||
statusable,
|
||||
receivingAccount,
|
||||
requestingAccount,
|
||||
)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// createNote handles a Create activity with a Note type.
|
||||
func (f *federatingDB) createNote(ctx context.Context, note vocab.ActivityStreamsNote, receivingAccount *gtsmodel.Account, requestingAccount *gtsmodel.Account) error {
|
||||
l := log.WithContext(ctx).
|
||||
WithFields(kv.Fields{
|
||||
{"receivingAccount", receivingAccount.URI},
|
||||
{"requestingAccount", requestingAccount.URI},
|
||||
}...)
|
||||
// createStatusable handles a Create activity for a Statusable.
|
||||
// This function won't insert anything in the database yet,
|
||||
// but will pass the Statusable (if appropriate) through to
|
||||
// the processor for further asynchronous processing.
|
||||
func (f *federatingDB) createStatusable(
|
||||
ctx context.Context,
|
||||
statusable ap.Statusable,
|
||||
receivingAccount *gtsmodel.Account,
|
||||
requestingAccount *gtsmodel.Account,
|
||||
) error {
|
||||
// Statusable must have an attributedTo.
|
||||
attrToProp := statusable.GetActivityStreamsAttributedTo()
|
||||
if attrToProp == nil {
|
||||
return gtserror.Newf("statusable had no attributedTo")
|
||||
}
|
||||
|
||||
// Check if we have a forward.
|
||||
// In other words, was the note posted to our inbox by at least one actor who actually created the note, or are they just forwarding it?
|
||||
// Statusable must have an ID.
|
||||
idProp := statusable.GetJSONLDId()
|
||||
if idProp == nil || !idProp.IsIRI() {
|
||||
return gtserror.Newf("statusable had no id, or id was not a URI")
|
||||
}
|
||||
|
||||
statusableURI := idProp.GetIRI()
|
||||
|
||||
// Check if we have a forward. In other words, was the
|
||||
// statusable posted to our inbox by at least one actor
|
||||
// who actually created it, or are they forwarding it?
|
||||
forward := true
|
||||
|
||||
// note should have an attributedTo
|
||||
noteAttributedTo := note.GetActivityStreamsAttributedTo()
|
||||
if noteAttributedTo == nil {
|
||||
return errors.New("createNote: note had no attributedTo")
|
||||
}
|
||||
|
||||
// compare the attributedTo(s) with the actor who posted this to our inbox
|
||||
for attributedToIter := noteAttributedTo.Begin(); attributedToIter != noteAttributedTo.End(); attributedToIter = attributedToIter.Next() {
|
||||
if !attributedToIter.IsIRI() {
|
||||
continue
|
||||
for iter := attrToProp.Begin(); iter != attrToProp.End(); iter = iter.Next() {
|
||||
actorURI, err := pub.ToId(iter)
|
||||
if err != nil {
|
||||
return gtserror.Newf("error extracting id from attributedTo entry: %w", err)
|
||||
}
|
||||
iri := attributedToIter.GetIRI()
|
||||
if requestingAccount.URI == iri.String() {
|
||||
// at least one creator of the note, and the actor who posted the note to our inbox, are the same, so it's not a forward
|
||||
|
||||
if requestingAccount.URI == actorURI.String() {
|
||||
// The actor who posted this statusable to our inbox is
|
||||
// (one of) its creator(s), so this is not a forward.
|
||||
forward = false
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// If we do have a forward, we should ignore the content for now and just dereference based on the URL/ID of the note instead, to get the note straight from the horse's mouth
|
||||
// Check if we already have a status entry
|
||||
// for this statusable, based on the ID/URI.
|
||||
statusableURIStr := statusableURI.String()
|
||||
status, err := f.state.DB.GetStatusByURI(ctx, statusableURIStr)
|
||||
if err != nil && !errors.Is(err, db.ErrNoEntries) {
|
||||
return gtserror.Newf("db error checking existence of status %s: %w", statusableURIStr, err)
|
||||
}
|
||||
|
||||
if status != nil {
|
||||
// We already had this status in the db, no need for further action.
|
||||
log.Trace(ctx, "status already exists: %s", statusableURIStr)
|
||||
return nil
|
||||
}
|
||||
|
||||
// If we do have a forward, we should ignore the content
|
||||
// and instead deref based on the URI of the statusable.
|
||||
//
|
||||
// In other words, don't automatically trust whoever sent
|
||||
// this status to us, but fetch the authentic article from
|
||||
// the server it originated from.
|
||||
if forward {
|
||||
l.Trace("note is a forward")
|
||||
id := note.GetJSONLDId()
|
||||
if !id.IsIRI() {
|
||||
// if the note id isn't an IRI, there's nothing we can do here
|
||||
return nil
|
||||
}
|
||||
// pass the note iri into the processor and have it do the dereferencing instead of doing it here
|
||||
// Pass the statusable URI (APIri) into the processor worker
|
||||
// and do the rest of the processing asynchronously.
|
||||
f.state.Workers.EnqueueFederator(ctx, messages.FromFederator{
|
||||
APObjectType: ap.ObjectNote,
|
||||
APActivityType: ap.ActivityCreate,
|
||||
APIri: id.GetIRI(),
|
||||
APIri: statusableURI,
|
||||
APObjectModel: nil,
|
||||
GTSModel: nil,
|
||||
ReceivingAccount: receivingAccount,
|
||||
|
@ -209,34 +244,58 @@ func (f *federatingDB) createNote(ctx context.Context, note vocab.ActivityStream
|
|||
return nil
|
||||
}
|
||||
|
||||
// if we reach this point, we know it's not a forwarded status, so proceed with processing it as normal
|
||||
|
||||
status, err := f.typeConverter.ASStatusToStatus(ctx, note)
|
||||
// This is a non-forwarded status we can trust the requester on,
|
||||
// convert this provided statusable data to a useable gtsmodel status.
|
||||
status, err = f.typeConverter.ASStatusToStatus(ctx, statusable)
|
||||
if err != nil {
|
||||
return fmt.Errorf("createNote: error converting note to status: %s", err)
|
||||
return gtserror.Newf("error converting statusable to status: %w", err)
|
||||
}
|
||||
|
||||
// id the status based on the time it was created
|
||||
statusID, err := id.NewULIDFromTime(status.CreatedAt)
|
||||
// Check whether we should accept this new status.
|
||||
accept, err := f.shouldAcceptStatusable(ctx,
|
||||
receivingAccount,
|
||||
requestingAccount,
|
||||
status,
|
||||
)
|
||||
if err != nil {
|
||||
return gtserror.Newf("error checking status acceptibility: %w", err)
|
||||
}
|
||||
|
||||
if !accept {
|
||||
// This is a status sent with no relation to receiver, i.e.
|
||||
// - receiving account does not follow requesting account
|
||||
// - received status does not mention receiving account
|
||||
//
|
||||
// We just pretend that all is fine (dog with cuppa, flames everywhere)
|
||||
log.Trace(ctx, "status failed acceptability check")
|
||||
return nil
|
||||
}
|
||||
|
||||
// ID the new status based on the time it was created.
|
||||
status.ID, err = id.NewULIDFromTime(status.CreatedAt)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
status.ID = statusID
|
||||
|
||||
// Put this newly parsed status in the database.
|
||||
if err := f.state.DB.PutStatus(ctx, status); err != nil {
|
||||
if errors.Is(err, db.ErrAlreadyExists) {
|
||||
// the status already exists in the database, which means we've already handled everything else,
|
||||
// so we can just return nil here and be done with it.
|
||||
// The status already exists in the database, which
|
||||
// means we've already processed it and some race
|
||||
// condition means we didn't catch it yet. We can
|
||||
// just return nil here and be done with it.
|
||||
return nil
|
||||
}
|
||||
// an actual error has happened
|
||||
return fmt.Errorf("createNote: database error inserting status: %s", err)
|
||||
return gtserror.Newf("db error inserting status: %w", err)
|
||||
}
|
||||
|
||||
// Do the rest of the processing asynchronously. The processor
|
||||
// will handle inserting/updating + further dereferencing the status.
|
||||
f.state.Workers.EnqueueFederator(ctx, messages.FromFederator{
|
||||
APObjectType: ap.ObjectNote,
|
||||
APActivityType: ap.ActivityCreate,
|
||||
APObjectModel: note,
|
||||
APIri: nil,
|
||||
APObjectModel: statusable,
|
||||
GTSModel: status,
|
||||
ReceivingAccount: receivingAccount,
|
||||
})
|
||||
|
@ -244,6 +303,26 @@ func (f *federatingDB) createNote(ctx context.Context, note vocab.ActivityStream
|
|||
return nil
|
||||
}
|
||||
|
||||
func (f *federatingDB) shouldAcceptStatusable(ctx context.Context, receiver *gtsmodel.Account, requester *gtsmodel.Account, status *gtsmodel.Status) (bool, error) {
|
||||
// Check whether status mentions the receiver,
|
||||
// this is the quickest check so perform it first.
|
||||
for _, mention := range status.Mentions {
|
||||
if mention.TargetAccountURI == receiver.URI {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
|
||||
// Check whether receiving account follows the requesting account.
|
||||
follows, err := f.state.DB.IsFollowing(ctx, receiver.ID, requester.ID)
|
||||
if err != nil {
|
||||
return false, gtserror.Newf("error checking follow status: %w", err)
|
||||
}
|
||||
|
||||
// Status will only be acceptable
|
||||
// if receiver follows requester.
|
||||
return follows, nil
|
||||
}
|
||||
|
||||
/*
|
||||
FOLLOW HANDLERS
|
||||
*/
|
||||
|
|
|
@ -54,7 +54,7 @@ func (suite *CreateTestSuite) TestCreateNote() {
|
|||
|
||||
// status should have some expected values
|
||||
suite.Equal(requestingAccount.ID, status.AccountID)
|
||||
suite.Equal("hey zork here's a new private note for you", status.Content)
|
||||
suite.Equal("@the_mighty_zork@localhost:8080 hey zork here's a new private note for you", status.Content)
|
||||
|
||||
// status should be in the database
|
||||
_, err = suite.db.GetStatusByID(context.Background(), status.ID)
|
||||
|
|
|
@ -247,8 +247,8 @@ func (c *Client) DoSigned(r *http.Request, sign SignFunc) (rsp *http.Response, e
|
|||
|
||||
// Rewind body reader and content-length if set.
|
||||
if rc, ok := r.Body.(*byteutil.ReadNopCloser); ok {
|
||||
rc.Rewind() // set len AFTER rewind
|
||||
r.ContentLength = int64(rc.Len())
|
||||
rc.Rewind()
|
||||
}
|
||||
|
||||
// Sign the outgoing request.
|
||||
|
|
|
@ -34,8 +34,7 @@ import (
|
|||
// Create processes the given form for creating a new account,
|
||||
// returning an oauth token for that account if successful.
|
||||
//
|
||||
// Fields on the form should have already been validated by the
|
||||
// caller, before this function is called.
|
||||
// Precondition: the form's fields should have already been validated and normalized by the caller.
|
||||
func (p *Processor) Create(
|
||||
ctx context.Context,
|
||||
appToken oauth2.TokenInfo,
|
||||
|
|
|
@ -31,6 +31,7 @@ import (
|
|||
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/log"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/messages"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/util"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
)
|
||||
|
||||
|
@ -427,10 +428,8 @@ func (p *Processor) deleteAccountPeripheral(ctx context.Context, account *gtsmod
|
|||
// names of all columns that are updated by it.
|
||||
func stubbifyAccount(account *gtsmodel.Account, origin string) []string {
|
||||
var (
|
||||
falseBool = func() *bool { b := false; return &b }
|
||||
trueBool = func() *bool { b := true; return &b }
|
||||
now = time.Now()
|
||||
never = time.Time{}
|
||||
now = time.Now()
|
||||
never = time.Time{}
|
||||
)
|
||||
|
||||
account.FetchedAt = never
|
||||
|
@ -444,17 +443,17 @@ func stubbifyAccount(account *gtsmodel.Account, origin string) []string {
|
|||
account.Fields = nil
|
||||
account.Note = ""
|
||||
account.NoteRaw = ""
|
||||
account.Memorial = falseBool()
|
||||
account.Memorial = util.Ptr(false)
|
||||
account.AlsoKnownAs = ""
|
||||
account.MovedToAccountID = ""
|
||||
account.Reason = ""
|
||||
account.Discoverable = falseBool()
|
||||
account.Discoverable = util.Ptr(false)
|
||||
account.StatusContentType = ""
|
||||
account.CustomCSS = ""
|
||||
account.SuspendedAt = now
|
||||
account.SuspensionOrigin = origin
|
||||
account.HideCollections = trueBool()
|
||||
account.EnableRSS = falseBool()
|
||||
account.HideCollections = util.Ptr(true)
|
||||
account.EnableRSS = util.Ptr(false)
|
||||
|
||||
return []string{
|
||||
"fetched_at",
|
||||
|
|
|
@ -23,7 +23,7 @@ import (
|
|||
|
||||
"github.com/stretchr/testify/suite"
|
||||
apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model"
|
||||
"github.com/superseriousbusiness/gotosocial/testrig"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/util"
|
||||
)
|
||||
|
||||
type FollowTestSuite struct {
|
||||
|
@ -40,10 +40,9 @@ func (suite *FollowTestSuite) TestUpdateExistingFollowChangeBoth() {
|
|||
// UPDATE "follows" AS "follow" SET "show_reblogs" = FALSE, "notify" = TRUE, "updated_at" = '2023-04-09 11:42:39.424705+00:00' WHERE ("follow"."id" = '01F8PY8RHWRQZV038T4E8T9YK8')
|
||||
relationship, err := suite.accountProcessor.FollowCreate(ctx, requestingAccount, &apimodel.AccountFollowRequest{
|
||||
ID: targetAccount.ID,
|
||||
Reblogs: testrig.FalseBool(),
|
||||
Notify: testrig.TrueBool(),
|
||||
Reblogs: util.Ptr(false),
|
||||
Notify: util.Ptr(true),
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
suite.FailNow(err.Error())
|
||||
}
|
||||
|
@ -62,9 +61,8 @@ func (suite *FollowTestSuite) TestUpdateExistingFollowChangeNotifyIgnoreReblogs(
|
|||
// UPDATE "follows" AS "follow" SET "notify" = TRUE, "updated_at" = '2023-04-09 11:40:33.827858+00:00' WHERE ("follow"."id" = '01F8PY8RHWRQZV038T4E8T9YK8')
|
||||
relationship, err := suite.accountProcessor.FollowCreate(ctx, requestingAccount, &apimodel.AccountFollowRequest{
|
||||
ID: targetAccount.ID,
|
||||
Notify: testrig.TrueBool(),
|
||||
Notify: util.Ptr(true),
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
suite.FailNow(err.Error())
|
||||
}
|
||||
|
@ -83,10 +81,9 @@ func (suite *FollowTestSuite) TestUpdateExistingFollowChangeNotifySetReblogs() {
|
|||
// UPDATE "follows" AS "follow" SET "notify" = TRUE, "updated_at" = '2023-04-09 11:40:33.827858+00:00' WHERE ("follow"."id" = '01F8PY8RHWRQZV038T4E8T9YK8')
|
||||
relationship, err := suite.accountProcessor.FollowCreate(ctx, requestingAccount, &apimodel.AccountFollowRequest{
|
||||
ID: targetAccount.ID,
|
||||
Notify: testrig.TrueBool(),
|
||||
Reblogs: testrig.TrueBool(),
|
||||
Notify: util.Ptr(true),
|
||||
Reblogs: util.Ptr(true),
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
suite.FailNow(err.Error())
|
||||
}
|
||||
|
@ -104,10 +101,9 @@ func (suite *FollowTestSuite) TestUpdateExistingFollowChangeNothing() {
|
|||
// Trace logs should show no update query.
|
||||
relationship, err := suite.accountProcessor.FollowCreate(ctx, requestingAccount, &apimodel.AccountFollowRequest{
|
||||
ID: targetAccount.ID,
|
||||
Notify: testrig.FalseBool(),
|
||||
Reblogs: testrig.TrueBool(),
|
||||
Notify: util.Ptr(false),
|
||||
Reblogs: util.Ptr(true),
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
suite.FailNow(err.Error())
|
||||
}
|
||||
|
@ -126,7 +122,6 @@ func (suite *FollowTestSuite) TestUpdateExistingFollowSetNothing() {
|
|||
relationship, err := suite.accountProcessor.FollowCreate(ctx, requestingAccount, &apimodel.AccountFollowRequest{
|
||||
ID: targetAccount.ID,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
suite.FailNow(err.Error())
|
||||
}
|
||||
|
|
|
@ -222,10 +222,11 @@ func (p *Processor) Update(ctx context.Context, account *gtsmodel.Account, form
|
|||
|
||||
if form.Source != nil {
|
||||
if form.Source.Language != nil {
|
||||
if err := validate.Language(*form.Source.Language); err != nil {
|
||||
language, err := validate.Language(*form.Source.Language)
|
||||
if err != nil {
|
||||
return nil, gtserror.NewErrorBadRequest(err)
|
||||
}
|
||||
account.Language = *form.Source.Language
|
||||
account.Language = language
|
||||
}
|
||||
|
||||
if form.Source.Sensitive != nil {
|
||||
|
|
|
@ -121,6 +121,15 @@ func (p *Processor) UserGet(ctx context.Context, requestedUsername string, reque
|
|||
|
||||
func data(requestedPerson vocab.ActivityStreamsPerson) (interface{}, gtserror.WithCode) {
|
||||
data, err := ap.Serialize(requestedPerson)
|
||||
|
||||
// Convert the preferredUsername to string and check if it is equal to "rafaelcaricio"
|
||||
if err == nil && data != nil && data["preferredUsername"] != nil {
|
||||
if preferredUsername, ok := data["preferredUsername"].(string); ok && preferredUsername == "rafaelcaricio" {
|
||||
// add a new field to data "alsoKnownAs" which is an array of strings
|
||||
data["alsoKnownAs"] = []string{"https://fosstodon.org/users/rafaelcaricio"}
|
||||
}
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
err := gtserror.Newf("error serializing person: %w", err)
|
||||
return nil, gtserror.NewErrorInternalError(err)
|
||||
|
|
|
@ -30,6 +30,7 @@ import (
|
|||
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/messages"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/stream"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/util"
|
||||
"github.com/superseriousbusiness/gotosocial/testrig"
|
||||
)
|
||||
|
||||
|
@ -63,20 +64,20 @@ func (suite *FromClientAPITestSuite) TestProcessStreamNewStatus() {
|
|||
EmojiIDs: []string{},
|
||||
CreatedAt: testrig.TimeMustParse("2021-10-20T11:36:45Z"),
|
||||
UpdatedAt: testrig.TimeMustParse("2021-10-20T11:36:45Z"),
|
||||
Local: testrig.TrueBool(),
|
||||
Local: util.Ptr(true),
|
||||
AccountURI: "http://localhost:8080/users/admin",
|
||||
AccountID: "01F8MH17FWEB39HZJ76B6VXSKF",
|
||||
InReplyToID: "",
|
||||
BoostOfID: "",
|
||||
ContentWarning: "",
|
||||
Visibility: gtsmodel.VisibilityFollowersOnly,
|
||||
Sensitive: testrig.FalseBool(),
|
||||
Sensitive: util.Ptr(false),
|
||||
Language: "en",
|
||||
CreatedWithApplicationID: "01F8MGXQRHYF5QPMTMXP78QC2F",
|
||||
Federated: testrig.FalseBool(),
|
||||
Boostable: testrig.TrueBool(),
|
||||
Replyable: testrig.TrueBool(),
|
||||
Likeable: testrig.TrueBool(),
|
||||
Federated: util.Ptr(false),
|
||||
Boostable: util.Ptr(true),
|
||||
Replyable: util.Ptr(true),
|
||||
Likeable: util.Ptr(true),
|
||||
ActivityStreamsType: ap.ObjectNote,
|
||||
}
|
||||
|
||||
|
@ -189,7 +190,7 @@ func (suite *FromClientAPITestSuite) TestProcessNewStatusWithNotification() {
|
|||
// that receiving account wants notifs when posting account posts.
|
||||
follow := >smodel.Follow{}
|
||||
*follow = *suite.testFollows["local_account_1_admin_account"]
|
||||
follow.Notify = testrig.TrueBool()
|
||||
follow.Notify = util.Ptr(true)
|
||||
if err := suite.db.UpdateFollow(ctx, follow); err != nil {
|
||||
suite.FailNow(err.Error())
|
||||
}
|
||||
|
@ -206,20 +207,20 @@ func (suite *FromClientAPITestSuite) TestProcessNewStatusWithNotification() {
|
|||
EmojiIDs: []string{},
|
||||
CreatedAt: testrig.TimeMustParse("2021-10-20T11:36:45Z"),
|
||||
UpdatedAt: testrig.TimeMustParse("2021-10-20T11:36:45Z"),
|
||||
Local: testrig.TrueBool(),
|
||||
Local: util.Ptr(true),
|
||||
AccountURI: "http://localhost:8080/users/admin",
|
||||
AccountID: "01F8MH17FWEB39HZJ76B6VXSKF",
|
||||
InReplyToID: "",
|
||||
BoostOfID: "",
|
||||
ContentWarning: "",
|
||||
Visibility: gtsmodel.VisibilityFollowersOnly,
|
||||
Sensitive: testrig.FalseBool(),
|
||||
Sensitive: util.Ptr(false),
|
||||
Language: "en",
|
||||
CreatedWithApplicationID: "01F8MGXQRHYF5QPMTMXP78QC2F",
|
||||
Federated: testrig.FalseBool(),
|
||||
Boostable: testrig.TrueBool(),
|
||||
Replyable: testrig.TrueBool(),
|
||||
Likeable: testrig.TrueBool(),
|
||||
Federated: util.Ptr(false),
|
||||
Boostable: util.Ptr(true),
|
||||
Replyable: util.Ptr(true),
|
||||
Likeable: util.Ptr(true),
|
||||
ActivityStreamsType: ap.ObjectNote,
|
||||
}
|
||||
|
||||
|
|
|
@ -108,20 +108,23 @@ func (p *Processor) ProcessFromFederator(ctx context.Context, federatorMsg messa
|
|||
|
||||
// processCreateStatusFromFederator handles Activity Create and Object Note.
|
||||
func (p *Processor) processCreateStatusFromFederator(ctx context.Context, federatorMsg messages.FromFederator) error {
|
||||
// Check the federatorMsg for either an already
|
||||
// dereferenced and converted status pinned to
|
||||
// the message, or an AP IRI that we need to deref.
|
||||
var (
|
||||
status *gtsmodel.Status
|
||||
err error
|
||||
|
||||
// Check the federatorMsg for either an already dereferenced
|
||||
// and converted status pinned to the message, or a forwarded
|
||||
// AP IRI that we still need to deref.
|
||||
forwarded = (federatorMsg.GTSModel == nil)
|
||||
)
|
||||
|
||||
if federatorMsg.GTSModel != nil {
|
||||
// Model is set, use that.
|
||||
status, err = p.statusFromGTSModel(ctx, federatorMsg)
|
||||
} else {
|
||||
// Model is not set, use IRI.
|
||||
if forwarded {
|
||||
// Model was not set, deref with IRI.
|
||||
// This will also cause the status to be inserted into the db.
|
||||
status, err = p.statusFromAPIRI(ctx, federatorMsg)
|
||||
} else {
|
||||
// Model is set, ensure we have the most up-to-date model.
|
||||
status, err = p.statusFromGTSModel(ctx, federatorMsg)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
|
|
|
@ -32,6 +32,7 @@ import (
|
|||
"github.com/superseriousbusiness/gotosocial/internal/id"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/messages"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/stream"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/util"
|
||||
"github.com/superseriousbusiness/gotosocial/testrig"
|
||||
)
|
||||
|
||||
|
@ -110,10 +111,10 @@ func (suite *FromFederatorTestSuite) TestProcessReplyMention() {
|
|||
InReplyToAccountID: repliedAccount.ID,
|
||||
Visibility: gtsmodel.VisibilityUnlocked,
|
||||
ActivityStreamsType: ap.ObjectNote,
|
||||
Federated: testrig.TrueBool(),
|
||||
Boostable: testrig.TrueBool(),
|
||||
Replyable: testrig.TrueBool(),
|
||||
Likeable: testrig.FalseBool(),
|
||||
Federated: util.Ptr(true),
|
||||
Boostable: util.Ptr(true),
|
||||
Replyable: util.Ptr(true),
|
||||
Likeable: util.Ptr(false),
|
||||
}
|
||||
|
||||
wssStream, errWithCode := suite.processor.Stream().Open(context.Background(), repliedAccount, stream.TimelineHome)
|
||||
|
@ -317,9 +318,9 @@ func (suite *FromFederatorTestSuite) TestProcessAccountDelete() {
|
|||
UpdatedAt: time.Now().Add(-1 * time.Hour),
|
||||
AccountID: deletedAccount.ID,
|
||||
TargetAccountID: receivingAccount.ID,
|
||||
ShowReblogs: testrig.TrueBool(),
|
||||
ShowReblogs: util.Ptr(true),
|
||||
URI: fmt.Sprintf("%s/follows/01FGRY72ASHBSET64353DPHK9T", deletedAccount.URI),
|
||||
Notify: testrig.FalseBool(),
|
||||
Notify: util.Ptr(false),
|
||||
}
|
||||
err := suite.db.Put(ctx, zorkFollowSatan)
|
||||
suite.NoError(err)
|
||||
|
@ -330,9 +331,9 @@ func (suite *FromFederatorTestSuite) TestProcessAccountDelete() {
|
|||
UpdatedAt: time.Now().Add(-1 * time.Hour),
|
||||
AccountID: receivingAccount.ID,
|
||||
TargetAccountID: deletedAccount.ID,
|
||||
ShowReblogs: testrig.TrueBool(),
|
||||
ShowReblogs: util.Ptr(true),
|
||||
URI: fmt.Sprintf("%s/follows/01FGRYAVAWWPP926J175QGM0WV", receivingAccount.URI),
|
||||
Notify: testrig.FalseBool(),
|
||||
Notify: util.Ptr(false),
|
||||
}
|
||||
err = suite.db.Put(ctx, satanFollowZork)
|
||||
suite.NoError(err)
|
||||
|
@ -405,9 +406,9 @@ func (suite *FromFederatorTestSuite) TestProcessFollowRequestLocked() {
|
|||
Account: originAccount,
|
||||
TargetAccountID: targetAccount.ID,
|
||||
TargetAccount: targetAccount,
|
||||
ShowReblogs: testrig.TrueBool(),
|
||||
ShowReblogs: util.Ptr(true),
|
||||
URI: fmt.Sprintf("%s/follows/01FGRYAVAWWPP926J175QGM0WV", originAccount.URI),
|
||||
Notify: testrig.FalseBool(),
|
||||
Notify: util.Ptr(false),
|
||||
}
|
||||
|
||||
err := suite.db.Put(ctx, satanFollowRequestTurtle)
|
||||
|
@ -462,9 +463,9 @@ func (suite *FromFederatorTestSuite) TestProcessFollowRequestUnlocked() {
|
|||
Account: originAccount,
|
||||
TargetAccountID: targetAccount.ID,
|
||||
TargetAccount: targetAccount,
|
||||
ShowReblogs: testrig.TrueBool(),
|
||||
ShowReblogs: util.Ptr(true),
|
||||
URI: fmt.Sprintf("%s/follows/01FGRYAVAWWPP926J175QGM0WV", originAccount.URI),
|
||||
Notify: testrig.FalseBool(),
|
||||
Notify: util.Ptr(false),
|
||||
}
|
||||
|
||||
err := suite.db.Put(ctx, satanFollowRequestTurtle)
|
||||
|
|
|
@ -28,6 +28,7 @@ import (
|
|||
apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/media"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/util"
|
||||
"github.com/superseriousbusiness/gotosocial/testrig"
|
||||
)
|
||||
|
||||
|
@ -68,7 +69,7 @@ func (suite *GetFileTestSuite) TestGetRemoteFileUncached() {
|
|||
|
||||
// uncache the file from local
|
||||
testAttachment := suite.testAttachments["remote_account_1_status_1_attachment_1"]
|
||||
testAttachment.Cached = testrig.FalseBool()
|
||||
testAttachment.Cached = util.Ptr(false)
|
||||
err := suite.db.UpdateByID(ctx, testAttachment, testAttachment.ID, "cached")
|
||||
suite.NoError(err)
|
||||
err = suite.storage.Delete(ctx, testAttachment.File.Path)
|
||||
|
@ -120,7 +121,7 @@ func (suite *GetFileTestSuite) TestGetRemoteFileUncachedInterrupted() {
|
|||
|
||||
// uncache the file from local
|
||||
testAttachment := suite.testAttachments["remote_account_1_status_1_attachment_1"]
|
||||
testAttachment.Cached = testrig.FalseBool()
|
||||
testAttachment.Cached = util.Ptr(false)
|
||||
err := suite.db.UpdateByID(ctx, testAttachment, testAttachment.ID, "cached")
|
||||
suite.NoError(err)
|
||||
err = suite.storage.Delete(ctx, testAttachment.File.Path)
|
||||
|
@ -177,7 +178,7 @@ func (suite *GetFileTestSuite) TestGetRemoteFileThumbnailUncached() {
|
|||
suite.NoError(err)
|
||||
|
||||
// uncache the file from local
|
||||
testAttachment.Cached = testrig.FalseBool()
|
||||
testAttachment.Cached = util.Ptr(false)
|
||||
err = suite.db.UpdateByID(ctx, testAttachment, testAttachment.ID, "cached")
|
||||
suite.NoError(err)
|
||||
err = suite.storage.Delete(ctx, testAttachment.File.Path)
|
||||
|
|
|
@ -37,6 +37,8 @@ import (
|
|||
)
|
||||
|
||||
// Create processes the given form to create a new status, returning the api model representation of that status if it's OK.
|
||||
//
|
||||
// Precondition: the form's fields should have already been validated and normalized by the caller.
|
||||
func (p *Processor) Create(ctx context.Context, account *gtsmodel.Account, application *gtsmodel.Application, form *apimodel.AdvancedStatusCreateForm) (*apimodel.Status, gtserror.WithCode) {
|
||||
accountURIs := uris.GenerateURIsForAccount(account.Username)
|
||||
thisStatusID := id.NewULID()
|
||||
|
@ -55,7 +57,6 @@ func (p *Processor) Create(ctx context.Context, account *gtsmodel.Account, appli
|
|||
ContentWarning: text.SanitizePlaintext(form.SpoilerText),
|
||||
ActivityStreamsType: ap.ObjectNote,
|
||||
Sensitive: &sensitive,
|
||||
Language: form.Language,
|
||||
CreatedWithApplicationID: application.ID,
|
||||
Text: form.Status,
|
||||
}
|
||||
|
|
|
@ -208,6 +208,40 @@ func (suite *StatusCreateTestSuite) TestProcessMediaDescriptionTooShort() {
|
|||
suite.Nil(apiStatus)
|
||||
}
|
||||
|
||||
func (suite *StatusCreateTestSuite) TestProcessLanguageWithScriptPart() {
|
||||
ctx := context.Background()
|
||||
|
||||
creatingAccount := suite.testAccounts["local_account_1"]
|
||||
creatingApplication := suite.testApplications["application_1"]
|
||||
|
||||
statusCreateForm := &apimodel.AdvancedStatusCreateForm{
|
||||
StatusCreateRequest: apimodel.StatusCreateRequest{
|
||||
Status: "你好世界", // hello world
|
||||
MediaIDs: []string{},
|
||||
Poll: nil,
|
||||
InReplyToID: "",
|
||||
Sensitive: false,
|
||||
SpoilerText: "",
|
||||
Visibility: apimodel.VisibilityPublic,
|
||||
ScheduledAt: "",
|
||||
Language: "zh-Hans",
|
||||
ContentType: apimodel.StatusContentTypePlain,
|
||||
},
|
||||
AdvancedVisibilityFlagsForm: apimodel.AdvancedVisibilityFlagsForm{
|
||||
Federated: nil,
|
||||
Boostable: nil,
|
||||
Replyable: nil,
|
||||
Likeable: nil,
|
||||
},
|
||||
}
|
||||
|
||||
apiStatus, err := suite.status.Create(ctx, creatingAccount, creatingApplication, statusCreateForm)
|
||||
suite.NoError(err)
|
||||
suite.NotNil(apiStatus)
|
||||
|
||||
suite.Equal("zh-Hans", *apiStatus.Language)
|
||||
}
|
||||
|
||||
func TestStatusCreateTestSuite(t *testing.T) {
|
||||
suite.Run(t, new(StatusCreateTestSuite))
|
||||
}
|
||||
|
|
|
@ -21,8 +21,6 @@ import (
|
|||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/url"
|
||||
|
||||
"github.com/miekg/dns"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/ap"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/config"
|
||||
|
@ -31,6 +29,9 @@ import (
|
|||
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/log"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/uris"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/util"
|
||||
"net/url"
|
||||
"time"
|
||||
)
|
||||
|
||||
func (c *converter) ASRepresentationToAccount(ctx context.Context, accountable ap.Accountable, accountDomain string) (*gtsmodel.Account, error) {
|
||||
|
@ -282,7 +283,7 @@ func (c *converter) ASStatusToStatus(ctx context.Context, statusable ap.Statusab
|
|||
//
|
||||
// Hashtags for later dereferencing.
|
||||
if hashtags, err := ap.ExtractHashtags(statusable); err != nil {
|
||||
l.Infof("error extracting hashtags: %q", err)
|
||||
l.Warnf("error extracting hashtags: %v", err)
|
||||
} else {
|
||||
status.Tags = hashtags
|
||||
}
|
||||
|
@ -291,7 +292,7 @@ func (c *converter) ASStatusToStatus(ctx context.Context, statusable ap.Statusab
|
|||
//
|
||||
// Custom emojis for later dereferencing.
|
||||
if emojis, err := ap.ExtractEmojis(statusable); err != nil {
|
||||
l.Infof("error extracting emojis: %q", err)
|
||||
l.Warnf("error extracting emojis: %v", err)
|
||||
} else {
|
||||
status.Emojis = emojis
|
||||
}
|
||||
|
@ -300,7 +301,7 @@ func (c *converter) ASStatusToStatus(ctx context.Context, statusable ap.Statusab
|
|||
//
|
||||
// Mentions of other accounts for later dereferencing.
|
||||
if mentions, err := ap.ExtractMentions(statusable); err != nil {
|
||||
l.Infof("error extracting mentions: %q", err)
|
||||
l.Warnf("error extracting mentions: %v", err)
|
||||
} else {
|
||||
status.Mentions = mentions
|
||||
}
|
||||
|
@ -321,7 +322,7 @@ func (c *converter) ASStatusToStatus(ctx context.Context, statusable ap.Statusab
|
|||
// db defaults, will fall back to now if not set.
|
||||
published, err := ap.ExtractPublished(statusable)
|
||||
if err != nil {
|
||||
l.Infof("error extracting published: %q", err)
|
||||
l.Warnf("error extracting published: %v", err)
|
||||
} else {
|
||||
status.CreatedAt = published
|
||||
status.UpdatedAt = published
|
||||
|
@ -396,11 +397,10 @@ func (c *converter) ASStatusToStatus(ctx context.Context, statusable ap.Statusab
|
|||
// TODO: a lot of work to be done here -- a new type
|
||||
// needs to be created for this in go-fed/activity.
|
||||
// Until this is implemented, assume all true.
|
||||
var trueBool = func() *bool { b := true; return &b }
|
||||
status.Federated = trueBool()
|
||||
status.Boostable = trueBool()
|
||||
status.Replyable = trueBool()
|
||||
status.Likeable = trueBool()
|
||||
status.Federated = util.Ptr(true)
|
||||
status.Boostable = util.Ptr(true)
|
||||
status.Replyable = util.Ptr(true)
|
||||
status.Likeable = util.Ptr(true)
|
||||
|
||||
// status.Sensitive
|
||||
status.Sensitive = func() *bool {
|
||||
|
@ -630,8 +630,8 @@ func (c *converter) ASAnnounceToStatus(ctx context.Context, announceable ap.Anno
|
|||
// Extract published time for the boost.
|
||||
published, err := ap.ExtractPublished(announceable)
|
||||
if err != nil {
|
||||
err = gtserror.Newf("error extracting published: %w", err)
|
||||
return nil, isNew, err
|
||||
//err = gtserror.Newf("error extracting published: %w", err)
|
||||
published = time.Now().UTC()
|
||||
}
|
||||
status.CreatedAt = published
|
||||
status.UpdatedAt = published
|
||||
|
|
23
internal/util/ptr.go
Normal file
23
internal/util/ptr.go
Normal file
|
@ -0,0 +1,23 @@
|
|||
// GoToSocial
|
||||
// Copyright (C) GoToSocial Authors admin@gotosocial.org
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package util
|
||||
|
||||
// Ptr returns a pointer to the passed in type
|
||||
func Ptr[T any](t T) *T {
|
||||
return &t
|
||||
}
|
|
@ -99,14 +99,19 @@ func Email(email string) error {
|
|||
return err
|
||||
}
|
||||
|
||||
// Language checks that the given language string is a 2- or 3-letter ISO 639 code.
|
||||
// Returns an error if the language cannot be parsed. See: https://pkg.go.dev/golang.org/x/text/language
|
||||
func Language(lang string) error {
|
||||
// Language checks that the given language string is a valid, if not necessarily canonical, BCP 47 language tag.
|
||||
// Returns a canonicalized version of the tag if the language can be parsed.
|
||||
// Returns an error if the language cannot be parsed.
|
||||
// See: https://pkg.go.dev/golang.org/x/text/language
|
||||
func Language(lang string) (string, error) {
|
||||
if lang == "" {
|
||||
return errors.New("no language provided")
|
||||
return "", errors.New("no language provided")
|
||||
}
|
||||
_, err := language.ParseBase(lang)
|
||||
return err
|
||||
parsed, err := language.Parse(lang)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return parsed.String(), err
|
||||
}
|
||||
|
||||
// SignUpReason checks that a sufficient reason is given for a server signup request
|
||||
|
|
|
@ -159,60 +159,39 @@ func (suite *ValidationTestSuite) TestValidateEmail() {
|
|||
}
|
||||
|
||||
func (suite *ValidationTestSuite) TestValidateLanguage() {
|
||||
empty := ""
|
||||
notALanguage := "this isn't a language at all!"
|
||||
english := "en"
|
||||
capitalEnglish := "EN"
|
||||
arabic3Letters := "ara"
|
||||
mixedCapsEnglish := "eN"
|
||||
englishUS := "en-us"
|
||||
dutch := "nl"
|
||||
german := "de"
|
||||
var err error
|
||||
|
||||
err = validate.Language(empty)
|
||||
if suite.Error(err) {
|
||||
suite.Equal(errors.New("no language provided"), err)
|
||||
testCases := []struct {
|
||||
name, input, expected, err string
|
||||
}{
|
||||
{name: "empty", err: "no language provided"},
|
||||
{name: "notALanguage", input: "this isn't a language at all!", err: "language: tag is not well-formed"},
|
||||
{name: "english", input: "en", expected: "en"},
|
||||
// Should be all lowercase
|
||||
{name: "capitalEnglish", input: "EN", expected: "en"},
|
||||
// Overlong, should be in ISO 639-1 format
|
||||
{name: "arabic3Letters", input: "ara", expected: "ar"},
|
||||
// Should be all lowercase
|
||||
{name: "mixedCapsEnglish", input: "eN", expected: "en"},
|
||||
// Region should be capitalized
|
||||
{name: "englishUS", input: "en-us", expected: "en-US"},
|
||||
{name: "dutch", input: "nl", expected: "nl"},
|
||||
{name: "german", input: "de", expected: "de"},
|
||||
{name: "chinese", input: "zh", expected: "zh"},
|
||||
{name: "chineseSimplified", input: "zh-Hans", expected: "zh-Hans"},
|
||||
{name: "chineseTraditional", input: "zh-Hant", expected: "zh-Hant"},
|
||||
}
|
||||
|
||||
err = validate.Language(notALanguage)
|
||||
if suite.Error(err) {
|
||||
suite.Equal(errors.New("language: tag is not well-formed"), err)
|
||||
}
|
||||
|
||||
err = validate.Language(english)
|
||||
if suite.NoError(err) {
|
||||
suite.Equal(nil, err)
|
||||
}
|
||||
|
||||
err = validate.Language(capitalEnglish)
|
||||
if suite.NoError(err) {
|
||||
suite.Equal(nil, err)
|
||||
}
|
||||
|
||||
err = validate.Language(arabic3Letters)
|
||||
if suite.NoError(err) {
|
||||
suite.Equal(nil, err)
|
||||
}
|
||||
|
||||
err = validate.Language(mixedCapsEnglish)
|
||||
if suite.NoError(err) {
|
||||
suite.Equal(nil, err)
|
||||
}
|
||||
|
||||
err = validate.Language(englishUS)
|
||||
if suite.Error(err) {
|
||||
suite.Equal(errors.New("language: tag is not well-formed"), err)
|
||||
}
|
||||
|
||||
err = validate.Language(dutch)
|
||||
if suite.NoError(err) {
|
||||
suite.Equal(nil, err)
|
||||
}
|
||||
|
||||
err = validate.Language(german)
|
||||
if suite.NoError(err) {
|
||||
suite.Equal(nil, err)
|
||||
for _, testCase := range testCases {
|
||||
testCase := testCase
|
||||
suite.Run(testCase.name, func() {
|
||||
actual, actualErr := validate.Language(testCase.input)
|
||||
if testCase.err == "" {
|
||||
suite.Equal(testCase.expected, actual)
|
||||
suite.NoError(actualErr)
|
||||
} else {
|
||||
suite.Empty(actual)
|
||||
suite.EqualError(actualErr, testCase.err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -25,6 +25,7 @@ import (
|
|||
"github.com/stretchr/testify/suite"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/ap"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/util"
|
||||
"github.com/superseriousbusiness/gotosocial/testrig"
|
||||
)
|
||||
|
||||
|
@ -115,7 +116,7 @@ func (suite *StatusStatusHomeTimelineableTestSuite) TestThread() {
|
|||
Content: "nbnbdy expects dog",
|
||||
CreatedAt: testrig.TimeMustParse("2021-09-20T12:41:37+02:00"),
|
||||
UpdatedAt: testrig.TimeMustParse("2021-09-20T12:41:37+02:00"),
|
||||
Local: testrig.FalseBool(),
|
||||
Local: util.Ptr(false),
|
||||
AccountURI: "http://localhost:8080/users/the_mighty_zork",
|
||||
AccountID: threadParentAccount.ID,
|
||||
InReplyToID: originalStatus.ID,
|
||||
|
@ -124,13 +125,13 @@ func (suite *StatusStatusHomeTimelineableTestSuite) TestThread() {
|
|||
BoostOfID: "",
|
||||
ContentWarning: "",
|
||||
Visibility: gtsmodel.VisibilityFollowersOnly,
|
||||
Sensitive: testrig.FalseBool(),
|
||||
Sensitive: util.Ptr(false),
|
||||
Language: "en",
|
||||
CreatedWithApplicationID: "",
|
||||
Federated: testrig.TrueBool(),
|
||||
Boostable: testrig.TrueBool(),
|
||||
Replyable: testrig.TrueBool(),
|
||||
Likeable: testrig.TrueBool(),
|
||||
Federated: util.Ptr(true),
|
||||
Boostable: util.Ptr(true),
|
||||
Replyable: util.Ptr(true),
|
||||
Likeable: util.Ptr(true),
|
||||
ActivityStreamsType: ap.ObjectNote,
|
||||
}
|
||||
if err := suite.db.PutStatus(ctx, firstReplyStatus); err != nil {
|
||||
|
@ -168,7 +169,7 @@ func (suite *StatusStatusHomeTimelineableTestSuite) TestChainReplyFollowersOnly(
|
|||
Content: "didn't expect dog",
|
||||
CreatedAt: testrig.TimeMustParse("2021-09-20T12:40:37+02:00"),
|
||||
UpdatedAt: testrig.TimeMustParse("2021-09-20T12:40:37+02:00"),
|
||||
Local: testrig.FalseBool(),
|
||||
Local: util.Ptr(false),
|
||||
AccountURI: "http://fossbros-anonymous.io/users/foss_satan",
|
||||
AccountID: originalStatusParent.ID,
|
||||
InReplyToID: "",
|
||||
|
@ -177,13 +178,13 @@ func (suite *StatusStatusHomeTimelineableTestSuite) TestChainReplyFollowersOnly(
|
|||
BoostOfID: "",
|
||||
ContentWarning: "",
|
||||
Visibility: gtsmodel.VisibilityFollowersOnly,
|
||||
Sensitive: testrig.FalseBool(),
|
||||
Sensitive: util.Ptr(false),
|
||||
Language: "en",
|
||||
CreatedWithApplicationID: "",
|
||||
Federated: testrig.TrueBool(),
|
||||
Boostable: testrig.TrueBool(),
|
||||
Replyable: testrig.TrueBool(),
|
||||
Likeable: testrig.TrueBool(),
|
||||
Federated: util.Ptr(true),
|
||||
Boostable: util.Ptr(true),
|
||||
Replyable: util.Ptr(true),
|
||||
Likeable: util.Ptr(true),
|
||||
ActivityStreamsType: ap.ObjectNote,
|
||||
}
|
||||
if err := suite.db.PutStatus(ctx, originalStatus); err != nil {
|
||||
|
@ -202,7 +203,7 @@ func (suite *StatusStatusHomeTimelineableTestSuite) TestChainReplyFollowersOnly(
|
|||
Content: "nbnbdy expects dog",
|
||||
CreatedAt: testrig.TimeMustParse("2021-09-20T12:41:37+02:00"),
|
||||
UpdatedAt: testrig.TimeMustParse("2021-09-20T12:41:37+02:00"),
|
||||
Local: testrig.FalseBool(),
|
||||
Local: util.Ptr(false),
|
||||
AccountURI: "http://localhost:8080/users/the_mighty_zork",
|
||||
AccountID: replyingAccount.ID,
|
||||
InReplyToID: originalStatus.ID,
|
||||
|
@ -211,13 +212,13 @@ func (suite *StatusStatusHomeTimelineableTestSuite) TestChainReplyFollowersOnly(
|
|||
BoostOfID: "",
|
||||
ContentWarning: "",
|
||||
Visibility: gtsmodel.VisibilityFollowersOnly,
|
||||
Sensitive: testrig.FalseBool(),
|
||||
Sensitive: util.Ptr(false),
|
||||
Language: "en",
|
||||
CreatedWithApplicationID: "",
|
||||
Federated: testrig.TrueBool(),
|
||||
Boostable: testrig.TrueBool(),
|
||||
Replyable: testrig.TrueBool(),
|
||||
Likeable: testrig.TrueBool(),
|
||||
Federated: util.Ptr(true),
|
||||
Boostable: util.Ptr(true),
|
||||
Replyable: util.Ptr(true),
|
||||
Likeable: util.Ptr(true),
|
||||
ActivityStreamsType: ap.ObjectNote,
|
||||
}
|
||||
if err := suite.db.PutStatus(ctx, firstReplyStatus); err != nil {
|
||||
|
@ -236,7 +237,7 @@ func (suite *StatusStatusHomeTimelineableTestSuite) TestChainReplyFollowersOnly(
|
|||
Content: "*nobody",
|
||||
CreatedAt: testrig.TimeMustParse("2021-09-20T12:42:37+02:00"),
|
||||
UpdatedAt: testrig.TimeMustParse("2021-09-20T12:42:37+02:00"),
|
||||
Local: testrig.FalseBool(),
|
||||
Local: util.Ptr(false),
|
||||
AccountURI: "http://localhost:8080/users/the_mighty_zork",
|
||||
AccountID: replyingAccount.ID,
|
||||
InReplyToID: firstReplyStatus.ID,
|
||||
|
@ -245,13 +246,13 @@ func (suite *StatusStatusHomeTimelineableTestSuite) TestChainReplyFollowersOnly(
|
|||
BoostOfID: "",
|
||||
ContentWarning: "",
|
||||
Visibility: gtsmodel.VisibilityFollowersOnly,
|
||||
Sensitive: testrig.FalseBool(),
|
||||
Sensitive: util.Ptr(false),
|
||||
Language: "en",
|
||||
CreatedWithApplicationID: "",
|
||||
Federated: testrig.TrueBool(),
|
||||
Boostable: testrig.TrueBool(),
|
||||
Replyable: testrig.TrueBool(),
|
||||
Likeable: testrig.TrueBool(),
|
||||
Federated: util.Ptr(true),
|
||||
Boostable: util.Ptr(true),
|
||||
Replyable: util.Ptr(true),
|
||||
Likeable: util.Ptr(true),
|
||||
ActivityStreamsType: ap.ObjectNote,
|
||||
}
|
||||
if err := suite.db.PutStatus(ctx, secondReplyStatus); err != nil {
|
||||
|
@ -281,7 +282,7 @@ func (suite *StatusStatusHomeTimelineableTestSuite) TestChainReplyPublicAndUnloc
|
|||
Content: "didn't expect dog",
|
||||
CreatedAt: testrig.TimeMustParse("2021-09-20T12:40:37+02:00"),
|
||||
UpdatedAt: testrig.TimeMustParse("2021-09-20T12:40:37+02:00"),
|
||||
Local: testrig.FalseBool(),
|
||||
Local: util.Ptr(false),
|
||||
AccountURI: "http://fossbros-anonymous.io/users/foss_satan",
|
||||
AccountID: originalStatusParent.ID,
|
||||
InReplyToID: "",
|
||||
|
@ -290,13 +291,13 @@ func (suite *StatusStatusHomeTimelineableTestSuite) TestChainReplyPublicAndUnloc
|
|||
BoostOfID: "",
|
||||
ContentWarning: "",
|
||||
Visibility: gtsmodel.VisibilityUnlocked,
|
||||
Sensitive: testrig.FalseBool(),
|
||||
Sensitive: util.Ptr(false),
|
||||
Language: "en",
|
||||
CreatedWithApplicationID: "",
|
||||
Federated: testrig.TrueBool(),
|
||||
Boostable: testrig.TrueBool(),
|
||||
Replyable: testrig.TrueBool(),
|
||||
Likeable: testrig.TrueBool(),
|
||||
Federated: util.Ptr(true),
|
||||
Boostable: util.Ptr(true),
|
||||
Replyable: util.Ptr(true),
|
||||
Likeable: util.Ptr(true),
|
||||
ActivityStreamsType: ap.ObjectNote,
|
||||
}
|
||||
if err := suite.db.PutStatus(ctx, originalStatus); err != nil {
|
||||
|
@ -315,7 +316,7 @@ func (suite *StatusStatusHomeTimelineableTestSuite) TestChainReplyPublicAndUnloc
|
|||
Content: "nbnbdy expects dog",
|
||||
CreatedAt: testrig.TimeMustParse("2021-09-20T12:41:37+02:00"),
|
||||
UpdatedAt: testrig.TimeMustParse("2021-09-20T12:41:37+02:00"),
|
||||
Local: testrig.FalseBool(),
|
||||
Local: util.Ptr(false),
|
||||
AccountURI: "http://localhost:8080/users/the_mighty_zork",
|
||||
AccountID: replyingAccount.ID,
|
||||
InReplyToID: originalStatus.ID,
|
||||
|
@ -324,13 +325,13 @@ func (suite *StatusStatusHomeTimelineableTestSuite) TestChainReplyPublicAndUnloc
|
|||
BoostOfID: "",
|
||||
ContentWarning: "",
|
||||
Visibility: gtsmodel.VisibilityPublic,
|
||||
Sensitive: testrig.FalseBool(),
|
||||
Sensitive: util.Ptr(false),
|
||||
Language: "en",
|
||||
CreatedWithApplicationID: "",
|
||||
Federated: testrig.TrueBool(),
|
||||
Boostable: testrig.TrueBool(),
|
||||
Replyable: testrig.TrueBool(),
|
||||
Likeable: testrig.TrueBool(),
|
||||
Federated: util.Ptr(true),
|
||||
Boostable: util.Ptr(true),
|
||||
Replyable: util.Ptr(true),
|
||||
Likeable: util.Ptr(true),
|
||||
ActivityStreamsType: ap.ObjectNote,
|
||||
}
|
||||
if err := suite.db.PutStatus(ctx, firstReplyStatus); err != nil {
|
||||
|
@ -349,7 +350,7 @@ func (suite *StatusStatusHomeTimelineableTestSuite) TestChainReplyPublicAndUnloc
|
|||
Content: "*nobody",
|
||||
CreatedAt: testrig.TimeMustParse("2021-09-20T12:42:37+02:00"),
|
||||
UpdatedAt: testrig.TimeMustParse("2021-09-20T12:42:37+02:00"),
|
||||
Local: testrig.FalseBool(),
|
||||
Local: util.Ptr(false),
|
||||
AccountURI: "http://localhost:8080/users/the_mighty_zork",
|
||||
AccountID: replyingAccount.ID,
|
||||
InReplyToID: firstReplyStatus.ID,
|
||||
|
@ -358,13 +359,13 @@ func (suite *StatusStatusHomeTimelineableTestSuite) TestChainReplyPublicAndUnloc
|
|||
BoostOfID: "",
|
||||
ContentWarning: "",
|
||||
Visibility: gtsmodel.VisibilityUnlocked,
|
||||
Sensitive: testrig.FalseBool(),
|
||||
Sensitive: util.Ptr(false),
|
||||
Language: "en",
|
||||
CreatedWithApplicationID: "",
|
||||
Federated: testrig.TrueBool(),
|
||||
Boostable: testrig.TrueBool(),
|
||||
Replyable: testrig.TrueBool(),
|
||||
Likeable: testrig.TrueBool(),
|
||||
Federated: util.Ptr(true),
|
||||
Boostable: util.Ptr(true),
|
||||
Replyable: util.Ptr(true),
|
||||
Likeable: util.Ptr(true),
|
||||
ActivityStreamsType: ap.ObjectNote,
|
||||
}
|
||||
if err := suite.db.PutStatus(ctx, secondReplyStatus); err != nil {
|
||||
|
|
|
@ -27,29 +27,43 @@ const (
|
|||
robotsPath = "/robots.txt"
|
||||
robotsMetaAllowSome = "nofollow, noarchive, nositelinkssearchbox, max-image-preview:standard" // https://developers.google.com/search/docs/crawling-indexing/robots-meta-tag#robotsmeta
|
||||
robotsTxt = `# GoToSocial robots.txt -- to edit, see internal/web/robots.go
|
||||
# more info @ https://developers.google.com/search/docs/crawling-indexing/robots/intro
|
||||
# More info @ https://developers.google.com/search/docs/crawling-indexing/robots/intro
|
||||
|
||||
# Before we commence, a giant fuck you to ChatGPT in particular.
|
||||
# https://platform.openai.com/docs/gptbot
|
||||
User-agent: GPTBot
|
||||
Disallow: /
|
||||
|
||||
# Rules for everything else.
|
||||
User-agent: *
|
||||
Crawl-delay: 500
|
||||
# api stuff
|
||||
|
||||
# API endpoints.
|
||||
Disallow: /api/
|
||||
# auth/login stuff
|
||||
|
||||
# Auth/login endpoints.
|
||||
Disallow: /auth/
|
||||
Disallow: /oauth/
|
||||
Disallow: /check_your_email
|
||||
Disallow: /wait_for_approval
|
||||
Disallow: /account_disabled
|
||||
# well known stuff
|
||||
|
||||
# Well-known endpoints.
|
||||
Disallow: /.well-known/
|
||||
# files
|
||||
|
||||
# Fileserver/media.
|
||||
Disallow: /fileserver/
|
||||
# s2s AP stuff
|
||||
|
||||
# Fedi S2S API endpoints.
|
||||
Disallow: /users/
|
||||
Disallow: /emoji/
|
||||
# panels
|
||||
|
||||
# Settings panels.
|
||||
Disallow: /admin
|
||||
Disallow: /user
|
||||
Disallow: /settings/
|
||||
# domain blocklist
|
||||
|
||||
# Domain blocklist.
|
||||
Disallow: /about/suspended`
|
||||
)
|
||||
|
||||
|
|
|
@ -2,6 +2,11 @@
|
|||
|
||||
set -e
|
||||
|
||||
# Ensure test args are set.
|
||||
ARGS=${@}; [ -z "$ARGS" ] && \
|
||||
ARGS='./...'
|
||||
|
||||
# Database config.
|
||||
DB_NAME='postgres'
|
||||
DB_USER='postgres'
|
||||
DB_PASS='postgres'
|
||||
|
@ -34,4 +39,4 @@ GTS_DB_PORT=${DB_PORT} \
|
|||
GTS_DB_USER=${DB_USER} \
|
||||
GTS_DB_PASSWORD=${DB_PASS} \
|
||||
GTS_DB_DATABASE=${DB_NAME} \
|
||||
go test ./... -p 1 ${@}
|
||||
go test ./... -p 1 ${ARGS}
|
|
@ -2,6 +2,11 @@
|
|||
|
||||
set -e
|
||||
|
||||
# Ensure test args are set.
|
||||
ARGS=${@}; [ -z "$ARGS" ] && \
|
||||
ARGS='./...'
|
||||
|
||||
# Run the SQLite tests.
|
||||
GTS_DB_TYPE=sqlite \
|
||||
GTS_DB_ADDRESS=':memory:' \
|
||||
go test ./... ${@}
|
||||
go test ${ARGS}
|
File diff suppressed because it is too large
Load diff
2
vendor/github.com/tdewolff/minify/v2/README.md
generated
vendored
2
vendor/github.com/tdewolff/minify/v2/README.md
generated
vendored
|
@ -79,7 +79,7 @@ Minifiers or bindings to minifiers exist in almost all programming languages. So
|
|||
This minifier proves to be that fast and extensive minifier that can handle HTML and any other filetype it may contain (CSS, JS, ...). It is usually orders of magnitude faster than existing minifiers.
|
||||
|
||||
## Installation
|
||||
Make sure you have [Git](https://git-scm.com/) and [Go](https://golang.org/dl/) (1.13 or higher) installed, run
|
||||
Make sure you have [Git](https://git-scm.com/) and [Go](https://golang.org/dl/) (1.18 or higher) installed, run
|
||||
```
|
||||
mkdir Project
|
||||
cd Project
|
||||
|
|
941
vendor/github.com/tdewolff/minify/v2/html/hash.go
generated
vendored
941
vendor/github.com/tdewolff/minify/v2/html/hash.go
generated
vendored
|
@ -11,238 +11,254 @@ type Hash uint32
|
|||
// Unique hash definitions to be used instead of strings
|
||||
const (
|
||||
A Hash = 0x1 // a
|
||||
Abbr Hash = 0x37a04 // abbr
|
||||
Abbr Hash = 0x3b804 // abbr
|
||||
About Hash = 0x5 // about
|
||||
Accept Hash = 0x1106 // accept
|
||||
Accept_Charset Hash = 0x110e // accept-charset
|
||||
Action Hash = 0x23f06 // action
|
||||
Address Hash = 0x5a07 // address
|
||||
Align Hash = 0x32705 // align
|
||||
Alink Hash = 0x7005 // alink
|
||||
Allowfullscreen Hash = 0x2ad0f // allowfullscreen
|
||||
Amp_Boilerplate Hash = 0x610f // amp-boilerplate
|
||||
Area Hash = 0x1e304 // area
|
||||
Acronym Hash = 0x4a07 // acronym
|
||||
Action Hash = 0x21d06 // action
|
||||
Address Hash = 0x7807 // address
|
||||
Align Hash = 0x35b05 // align
|
||||
Alink Hash = 0x3a405 // alink
|
||||
Allowfullscreen Hash = 0x2e10f // allowfullscreen
|
||||
Amp_Boilerplate Hash = 0x7f0f // amp-boilerplate
|
||||
Applet Hash = 0xd706 // applet
|
||||
Area Hash = 0x2fd04 // area
|
||||
Article Hash = 0x2707 // article
|
||||
Aside Hash = 0xb405 // aside
|
||||
Async Hash = 0xac05 // async
|
||||
Audio Hash = 0xd105 // audio
|
||||
Autofocus Hash = 0xe409 // autofocus
|
||||
Autoplay Hash = 0x10808 // autoplay
|
||||
Axis Hash = 0x11004 // axis
|
||||
Aside Hash = 0x5b05 // aside
|
||||
Async Hash = 0x8e05 // async
|
||||
Audio Hash = 0x9605 // audio
|
||||
Autofocus Hash = 0xcc09 // autofocus
|
||||
Autoplay Hash = 0x10c08 // autoplay
|
||||
Axis Hash = 0x11404 // axis
|
||||
B Hash = 0x101 // b
|
||||
Background Hash = 0x300a // background
|
||||
Base Hash = 0x19604 // base
|
||||
Bb Hash = 0x37b02 // bb
|
||||
Bdi Hash = 0x7503 // bdi
|
||||
Bdo Hash = 0x31f03 // bdo
|
||||
Bgcolor Hash = 0x12607 // bgcolor
|
||||
Blockquote Hash = 0x13e0a // blockquote
|
||||
Base Hash = 0x17804 // base
|
||||
Basefont Hash = 0x17808 // basefont
|
||||
Bb Hash = 0x3b902 // bb
|
||||
Bdi Hash = 0x18403 // bdi
|
||||
Bdo Hash = 0x35303 // bdo
|
||||
Bgcolor Hash = 0x12a07 // bgcolor
|
||||
Big Hash = 0x13103 // big
|
||||
Blockquote Hash = 0x1340a // blockquote
|
||||
Body Hash = 0xd04 // body
|
||||
Br Hash = 0x37c02 // br
|
||||
Button Hash = 0x14806 // button
|
||||
Canvas Hash = 0xb006 // canvas
|
||||
Caption Hash = 0x21f07 // caption
|
||||
Br Hash = 0x36102 // br
|
||||
Button Hash = 0x13e06 // button
|
||||
Canvas Hash = 0x5706 // canvas
|
||||
Caption Hash = 0x1fe07 // caption
|
||||
Center Hash = 0xb706 // center
|
||||
Charset Hash = 0x1807 // charset
|
||||
Checked Hash = 0x1b307 // checked
|
||||
Cite Hash = 0xfb04 // cite
|
||||
Class Hash = 0x15905 // class
|
||||
Classid Hash = 0x15907 // classid
|
||||
Checked Hash = 0x19707 // checked
|
||||
Cite Hash = 0x9204 // cite
|
||||
Class Hash = 0x15105 // class
|
||||
Classid Hash = 0x15107 // classid
|
||||
Clear Hash = 0x2b05 // clear
|
||||
Code Hash = 0x19204 // code
|
||||
Codebase Hash = 0x19208 // codebase
|
||||
Codetype Hash = 0x1a408 // codetype
|
||||
Col Hash = 0x12803 // col
|
||||
Colgroup Hash = 0x1bb08 // colgroup
|
||||
Color Hash = 0x12805 // color
|
||||
Cols Hash = 0x1cf04 // cols
|
||||
Colspan Hash = 0x1cf07 // colspan
|
||||
Compact Hash = 0x1ec07 // compact
|
||||
Content Hash = 0x28407 // content
|
||||
Controls Hash = 0x20108 // controls
|
||||
Code Hash = 0x17404 // code
|
||||
Codebase Hash = 0x17408 // codebase
|
||||
Codetype Hash = 0x18808 // codetype
|
||||
Col Hash = 0x12c03 // col
|
||||
Colgroup Hash = 0x1af08 // colgroup
|
||||
Color Hash = 0x12c05 // color
|
||||
Cols Hash = 0x1c904 // cols
|
||||
Colspan Hash = 0x1c907 // colspan
|
||||
Compact Hash = 0x1d707 // compact
|
||||
Content Hash = 0x27b07 // content
|
||||
Controls Hash = 0x1e708 // controls
|
||||
Data Hash = 0x1f04 // data
|
||||
Datalist Hash = 0x1f08 // datalist
|
||||
Datatype Hash = 0x4d08 // datatype
|
||||
Dd Hash = 0x5b02 // dd
|
||||
Declare Hash = 0xb707 // declare
|
||||
Default Hash = 0x7f07 // default
|
||||
DefaultChecked Hash = 0x1730e // defaultChecked
|
||||
DefaultMuted Hash = 0x7f0c // defaultMuted
|
||||
DefaultSelected Hash = 0x8a0f // defaultSelected
|
||||
Defer Hash = 0x9805 // defer
|
||||
Del Hash = 0x10503 // del
|
||||
Details Hash = 0x15f07 // details
|
||||
Dfn Hash = 0x16c03 // dfn
|
||||
Dialog Hash = 0xa606 // dialog
|
||||
Dir Hash = 0x7603 // dir
|
||||
Disabled Hash = 0x18008 // disabled
|
||||
Div Hash = 0x18703 // div
|
||||
Dl Hash = 0x1b902 // dl
|
||||
Dt Hash = 0x23102 // dt
|
||||
Datatype Hash = 0xac08 // datatype
|
||||
Dd Hash = 0x7902 // dd
|
||||
Declare Hash = 0x5e07 // declare
|
||||
Default Hash = 0xeb07 // default
|
||||
DefaultChecked Hash = 0x2270e // defaultChecked
|
||||
DefaultMuted Hash = 0xeb0c // defaultMuted
|
||||
DefaultSelected Hash = 0xf60f // defaultSelected
|
||||
Defer Hash = 0x10405 // defer
|
||||
Del Hash = 0x37903 // del
|
||||
Details Hash = 0x15707 // details
|
||||
Dfn Hash = 0x16403 // dfn
|
||||
Dialog Hash = 0xc606 // dialog
|
||||
Dir Hash = 0x18503 // dir
|
||||
Disabled Hash = 0x19d08 // disabled
|
||||
Div Hash = 0x1a403 // div
|
||||
Dl Hash = 0x1e502 // dl
|
||||
Dt Hash = 0x21702 // dt
|
||||
Em Hash = 0x4302 // em
|
||||
Embed Hash = 0x4905 // embed
|
||||
Enabled Hash = 0x26c07 // enabled
|
||||
Enctype Hash = 0x1fa07 // enctype
|
||||
Face Hash = 0x5604 // face
|
||||
Fieldset Hash = 0x21408 // fieldset
|
||||
Figcaption Hash = 0x21c0a // figcaption
|
||||
Figure Hash = 0x22606 // figure
|
||||
Footer Hash = 0xdb06 // footer
|
||||
For Hash = 0x23b03 // for
|
||||
Form Hash = 0x23b04 // form
|
||||
Formaction Hash = 0x23b0a // formaction
|
||||
Formnovalidate Hash = 0x2450e // formnovalidate
|
||||
Frame Hash = 0x28c05 // frame
|
||||
Frameborder Hash = 0x28c0b // frameborder
|
||||
H1 Hash = 0x2e002 // h1
|
||||
H2 Hash = 0x25302 // h2
|
||||
H3 Hash = 0x25502 // h3
|
||||
H4 Hash = 0x25702 // h4
|
||||
H5 Hash = 0x25902 // h5
|
||||
H6 Hash = 0x25b02 // h6
|
||||
Head Hash = 0x2d204 // head
|
||||
Header Hash = 0x2d206 // header
|
||||
Hgroup Hash = 0x25d06 // hgroup
|
||||
Hidden Hash = 0x26806 // hidden
|
||||
Hr Hash = 0x32d02 // hr
|
||||
Href Hash = 0x32d04 // href
|
||||
Hreflang Hash = 0x32d08 // hreflang
|
||||
Html Hash = 0x27304 // html
|
||||
Http_Equiv Hash = 0x2770a // http-equiv
|
||||
Embed Hash = 0x37505 // embed
|
||||
Enabled Hash = 0x26307 // enabled
|
||||
Enctype Hash = 0x2a207 // enctype
|
||||
Face Hash = 0xb504 // face
|
||||
Fieldset Hash = 0x1f308 // fieldset
|
||||
Figcaption Hash = 0x1fb0a // figcaption
|
||||
Figure Hash = 0x20c06 // figure
|
||||
Font Hash = 0x17c04 // font
|
||||
Footer Hash = 0xa006 // footer
|
||||
For Hash = 0x21903 // for
|
||||
Form Hash = 0x21904 // form
|
||||
Formaction Hash = 0x2190a // formaction
|
||||
Formnovalidate Hash = 0x2350e // formnovalidate
|
||||
Frame Hash = 0x14505 // frame
|
||||
Frameborder Hash = 0x2830b // frameborder
|
||||
Frameset Hash = 0x14508 // frameset
|
||||
H1 Hash = 0x2d002 // h1
|
||||
H2 Hash = 0x24302 // h2
|
||||
H3 Hash = 0x24502 // h3
|
||||
H4 Hash = 0x24702 // h4
|
||||
H5 Hash = 0x24902 // h5
|
||||
H6 Hash = 0x24b02 // h6
|
||||
Head Hash = 0x2c204 // head
|
||||
Header Hash = 0x2c206 // header
|
||||
Hgroup Hash = 0x24d06 // hgroup
|
||||
Hidden Hash = 0x25f06 // hidden
|
||||
Hr Hash = 0x16802 // hr
|
||||
Href Hash = 0x16804 // href
|
||||
Hreflang Hash = 0x16808 // hreflang
|
||||
Html Hash = 0x26a04 // html
|
||||
Http_Equiv Hash = 0x26e0a // http-equiv
|
||||
I Hash = 0x2401 // i
|
||||
Icon Hash = 0x28304 // icon
|
||||
Id Hash = 0xb602 // id
|
||||
Iframe Hash = 0x28b06 // iframe
|
||||
Img Hash = 0x29703 // img
|
||||
Inert Hash = 0xf605 // inert
|
||||
Inlist Hash = 0x29a06 // inlist
|
||||
Input Hash = 0x2a405 // input
|
||||
Ins Hash = 0x2a903 // ins
|
||||
Ismap Hash = 0x11205 // ismap
|
||||
Itemscope Hash = 0xfc09 // itemscope
|
||||
Kbd Hash = 0x7403 // kbd
|
||||
Keygen Hash = 0x1f606 // keygen
|
||||
Label Hash = 0xbe05 // label
|
||||
Lang Hash = 0x33104 // lang
|
||||
Language Hash = 0x33108 // language
|
||||
Legend Hash = 0x2c506 // legend
|
||||
Icon Hash = 0x27a04 // icon
|
||||
Id Hash = 0x5d02 // id
|
||||
Iframe Hash = 0x28206 // iframe
|
||||
Image Hash = 0x28e05 // image
|
||||
Img Hash = 0x29303 // img
|
||||
Inert Hash = 0x5205 // inert
|
||||
Inlist Hash = 0x29606 // inlist
|
||||
Input Hash = 0x2a905 // input
|
||||
Ins Hash = 0x2ae03 // ins
|
||||
Ismap Hash = 0x11605 // ismap
|
||||
Itemscope Hash = 0xe209 // itemscope
|
||||
Kbd Hash = 0x18303 // kbd
|
||||
Keygen Hash = 0x29e06 // keygen
|
||||
Label Hash = 0x6505 // label
|
||||
Lang Hash = 0x16c04 // lang
|
||||
Language Hash = 0x16c08 // language
|
||||
Legend Hash = 0x31706 // legend
|
||||
Li Hash = 0x2302 // li
|
||||
Link Hash = 0x7104 // link
|
||||
Longdesc Hash = 0xc208 // longdesc
|
||||
Main Hash = 0xf404 // main
|
||||
Manifest Hash = 0x2bc08 // manifest
|
||||
Map Hash = 0xee03 // map
|
||||
Mark Hash = 0x2cb04 // mark
|
||||
Math Hash = 0x2cf04 // math
|
||||
Max Hash = 0x2d803 // max
|
||||
Maxlength Hash = 0x2d809 // maxlength
|
||||
Media Hash = 0xa405 // media
|
||||
Menu Hash = 0x12204 // menu
|
||||
Meta Hash = 0x2e204 // meta
|
||||
Meter Hash = 0x2f705 // meter
|
||||
Method Hash = 0x2fc06 // method
|
||||
Multiple Hash = 0x30208 // multiple
|
||||
Muted Hash = 0x30a05 // muted
|
||||
Name Hash = 0xa204 // name
|
||||
Nav Hash = 0x32403 // nav
|
||||
Nohref Hash = 0x32b06 // nohref
|
||||
Noresize Hash = 0x13608 // noresize
|
||||
Noscript Hash = 0x14d08 // noscript
|
||||
Noshade Hash = 0x16e07 // noshade
|
||||
Novalidate Hash = 0x2490a // novalidate
|
||||
Nowrap Hash = 0x1d506 // nowrap
|
||||
Object Hash = 0xd506 // object
|
||||
Ol Hash = 0xcb02 // ol
|
||||
Open Hash = 0x32104 // open
|
||||
Optgroup Hash = 0x35608 // optgroup
|
||||
Option Hash = 0x30f06 // option
|
||||
Link Hash = 0x3a504 // link
|
||||
Longdesc Hash = 0x6908 // longdesc
|
||||
Main Hash = 0x5004 // main
|
||||
Manifest Hash = 0x11e08 // manifest
|
||||
Map Hash = 0xd603 // map
|
||||
Mark Hash = 0x2b404 // mark
|
||||
Marquee Hash = 0x2b807 // marquee
|
||||
Math Hash = 0x2bf04 // math
|
||||
Max Hash = 0x2c803 // max
|
||||
Maxlength Hash = 0x2c809 // maxlength
|
||||
Media Hash = 0xc405 // media
|
||||
Menu Hash = 0xde04 // menu
|
||||
Menuitem Hash = 0xde08 // menuitem
|
||||
Meta Hash = 0x2d204 // meta
|
||||
Meter Hash = 0x30605 // meter
|
||||
Method Hash = 0x30b06 // method
|
||||
Multiple Hash = 0x31108 // multiple
|
||||
Muted Hash = 0x31d05 // muted
|
||||
Name Hash = 0xc204 // name
|
||||
Nav Hash = 0x35803 // nav
|
||||
Nobr Hash = 0x35f04 // nobr
|
||||
Noembed Hash = 0x37307 // noembed
|
||||
Noframes Hash = 0x14308 // noframes
|
||||
Nohref Hash = 0x16606 // nohref
|
||||
Noresize Hash = 0x1cf08 // noresize
|
||||
Noscript Hash = 0x20408 // noscript
|
||||
Noshade Hash = 0x22207 // noshade
|
||||
Novalidate Hash = 0x2390a // novalidate
|
||||
Nowrap Hash = 0x2ef06 // nowrap
|
||||
Object Hash = 0x9a06 // object
|
||||
Ol Hash = 0x7202 // ol
|
||||
Open Hash = 0x35504 // open
|
||||
Optgroup Hash = 0x39908 // optgroup
|
||||
Option Hash = 0x32206 // option
|
||||
Output Hash = 0x206 // output
|
||||
P Hash = 0x501 // p
|
||||
Param Hash = 0xf005 // param
|
||||
Pauseonexit Hash = 0x1160b // pauseonexit
|
||||
Picture Hash = 0x1c207 // picture
|
||||
Plaintext Hash = 0x1da09 // plaintext
|
||||
Poster Hash = 0x26206 // poster
|
||||
Pre Hash = 0x35d03 // pre
|
||||
Prefix Hash = 0x35d06 // prefix
|
||||
Profile Hash = 0x36407 // profile
|
||||
Progress Hash = 0x34208 // progress
|
||||
Property Hash = 0x31508 // property
|
||||
Q Hash = 0x14301 // q
|
||||
Param Hash = 0x11a05 // param
|
||||
Pauseonexit Hash = 0x1b60b // pauseonexit
|
||||
Picture Hash = 0x25207 // picture
|
||||
Plaintext Hash = 0x2f409 // plaintext
|
||||
Portal Hash = 0x3a006 // portal
|
||||
Poster Hash = 0x38c06 // poster
|
||||
Pre Hash = 0x38503 // pre
|
||||
Prefix Hash = 0x38506 // prefix
|
||||
Profile Hash = 0x32807 // profile
|
||||
Progress Hash = 0x32f08 // progress
|
||||
Property Hash = 0x33e08 // property
|
||||
Q Hash = 0x13901 // q
|
||||
Rb Hash = 0x2f02 // rb
|
||||
Readonly Hash = 0x1e408 // readonly
|
||||
Rel Hash = 0xbc03 // rel
|
||||
Required Hash = 0x22a08 // required
|
||||
Resource Hash = 0x1c708 // resource
|
||||
Rev Hash = 0x7803 // rev
|
||||
Reversed Hash = 0x7808 // reversed
|
||||
Rows Hash = 0x9c04 // rows
|
||||
Rowspan Hash = 0x9c07 // rowspan
|
||||
Rp Hash = 0x6a02 // rp
|
||||
Readonly Hash = 0x2fe08 // readonly
|
||||
Rel Hash = 0x6303 // rel
|
||||
Required Hash = 0x21008 // required
|
||||
Resource Hash = 0x25708 // resource
|
||||
Rev Hash = 0xa503 // rev
|
||||
Reversed Hash = 0xa508 // reversed
|
||||
Rows Hash = 0xbc04 // rows
|
||||
Rowspan Hash = 0xbc07 // rowspan
|
||||
Rp Hash = 0x8802 // rp
|
||||
Rt Hash = 0x2802 // rt
|
||||
Rtc Hash = 0xf903 // rtc
|
||||
Ruby Hash = 0xe004 // ruby
|
||||
Rules Hash = 0x12c05 // rules
|
||||
Rtc Hash = 0x5503 // rtc
|
||||
Ruby Hash = 0x10804 // ruby
|
||||
Rules Hash = 0x36205 // rules
|
||||
S Hash = 0x1c01 // s
|
||||
Samp Hash = 0x6004 // samp
|
||||
Scope Hash = 0x10005 // scope
|
||||
Scoped Hash = 0x10006 // scoped
|
||||
Script Hash = 0x14f06 // script
|
||||
Scrolling Hash = 0xc809 // scrolling
|
||||
Seamless Hash = 0x19808 // seamless
|
||||
Section Hash = 0x13007 // section
|
||||
Select Hash = 0x16506 // select
|
||||
Selected Hash = 0x16508 // selected
|
||||
Shape Hash = 0x19f05 // shape
|
||||
Size Hash = 0x13a04 // size
|
||||
Slot Hash = 0x20804 // slot
|
||||
Small Hash = 0x2ab05 // small
|
||||
Sortable Hash = 0x2ef08 // sortable
|
||||
Source Hash = 0x1c906 // source
|
||||
Span Hash = 0x9f04 // span
|
||||
Src Hash = 0x34903 // src
|
||||
Srcset Hash = 0x34906 // srcset
|
||||
Samp Hash = 0x7e04 // samp
|
||||
Scope Hash = 0xe605 // scope
|
||||
Scoped Hash = 0xe606 // scoped
|
||||
Script Hash = 0x20606 // script
|
||||
Scrolling Hash = 0x6f09 // scrolling
|
||||
Seamless Hash = 0x36608 // seamless
|
||||
Section Hash = 0x36d07 // section
|
||||
Select Hash = 0x15d06 // select
|
||||
Selected Hash = 0x15d08 // selected
|
||||
Shape Hash = 0x1ee05 // shape
|
||||
Size Hash = 0x1d304 // size
|
||||
Slot Hash = 0x2b004 // slot
|
||||
Small Hash = 0x2df05 // small
|
||||
Sortable Hash = 0x33608 // sortable
|
||||
Source Hash = 0x25906 // source
|
||||
Span Hash = 0xbf04 // span
|
||||
Src Hash = 0x34603 // src
|
||||
Srcset Hash = 0x34606 // srcset
|
||||
Start Hash = 0x2505 // start
|
||||
Strong Hash = 0x29e06 // strong
|
||||
Style Hash = 0x2c205 // style
|
||||
Sub Hash = 0x31d03 // sub
|
||||
Summary Hash = 0x33907 // summary
|
||||
Sup Hash = 0x34003 // sup
|
||||
Svg Hash = 0x34f03 // svg
|
||||
Tabindex Hash = 0x2e408 // tabindex
|
||||
Table Hash = 0x2f205 // table
|
||||
Strike Hash = 0x29a06 // strike
|
||||
Strong Hash = 0x12406 // strong
|
||||
Style Hash = 0x34c05 // style
|
||||
Sub Hash = 0x35103 // sub
|
||||
Summary Hash = 0x37c07 // summary
|
||||
Sup Hash = 0x38303 // sup
|
||||
Svg Hash = 0x39203 // svg
|
||||
Tabindex Hash = 0x2d408 // tabindex
|
||||
Table Hash = 0x33905 // table
|
||||
Target Hash = 0x706 // target
|
||||
Tbody Hash = 0xc05 // tbody
|
||||
Td Hash = 0x1e02 // td
|
||||
Template Hash = 0x4208 // template
|
||||
Text Hash = 0x1df04 // text
|
||||
Textarea Hash = 0x1df08 // textarea
|
||||
Tfoot Hash = 0xda05 // tfoot
|
||||
Th Hash = 0x2d102 // th
|
||||
Thead Hash = 0x2d105 // thead
|
||||
Time Hash = 0x12004 // time
|
||||
Title Hash = 0x15405 // title
|
||||
Tr Hash = 0x1f202 // tr
|
||||
Track Hash = 0x1f205 // track
|
||||
Translate Hash = 0x20b09 // translate
|
||||
Truespeed Hash = 0x23209 // truespeed
|
||||
Type Hash = 0x5104 // type
|
||||
Typemustmatch Hash = 0x1a80d // typemustmatch
|
||||
Typeof Hash = 0x5106 // typeof
|
||||
Text Hash = 0x2f904 // text
|
||||
Textarea Hash = 0x2f908 // textarea
|
||||
Tfoot Hash = 0x9f05 // tfoot
|
||||
Th Hash = 0x2c102 // th
|
||||
Thead Hash = 0x2c105 // thead
|
||||
Time Hash = 0xdc04 // time
|
||||
Title Hash = 0x14c05 // title
|
||||
Tr Hash = 0x12502 // tr
|
||||
Track Hash = 0x17f05 // track
|
||||
Translate Hash = 0x1c009 // translate
|
||||
Truespeed Hash = 0x1dd09 // truespeed
|
||||
Tt Hash = 0x14002 // tt
|
||||
Type Hash = 0xb004 // type
|
||||
Typemustmatch Hash = 0x18c0d // typemustmatch
|
||||
Typeof Hash = 0xb006 // typeof
|
||||
U Hash = 0x301 // u
|
||||
Ul Hash = 0x8302 // ul
|
||||
Ul Hash = 0xef02 // ul
|
||||
Undeterminate Hash = 0x370d // undeterminate
|
||||
Usemap Hash = 0xeb06 // usemap
|
||||
Valign Hash = 0x32606 // valign
|
||||
Value Hash = 0x18905 // value
|
||||
Valuetype Hash = 0x18909 // valuetype
|
||||
Var Hash = 0x28003 // var
|
||||
Video Hash = 0x35205 // video
|
||||
Visible Hash = 0x36b07 // visible
|
||||
Vlink Hash = 0x37205 // vlink
|
||||
Vocab Hash = 0x37705 // vocab
|
||||
Wbr Hash = 0x37e03 // wbr
|
||||
Xmlns Hash = 0x2eb05 // xmlns
|
||||
Xmp Hash = 0x36203 // xmp
|
||||
Usemap Hash = 0xd306 // usemap
|
||||
Valign Hash = 0x35a06 // valign
|
||||
Value Hash = 0x1a605 // value
|
||||
Valuetype Hash = 0x1a609 // valuetype
|
||||
Var Hash = 0x27703 // var
|
||||
Video Hash = 0x39505 // video
|
||||
Visible Hash = 0x3a907 // visible
|
||||
Vlink Hash = 0x3b005 // vlink
|
||||
Vocab Hash = 0x3b505 // vocab
|
||||
Wbr Hash = 0x3bc03 // wbr
|
||||
Xmlns Hash = 0x2db05 // xmlns
|
||||
Xmp Hash = 0x38a03 // xmp
|
||||
)
|
||||
|
||||
// String returns the hash' name.
|
||||
|
@ -288,256 +304,273 @@ NEXT:
|
|||
return 0
|
||||
}
|
||||
|
||||
const _Hash_hash0 = 0x9acb0442
|
||||
const _Hash_hash0 = 0x67ac9bb5
|
||||
const _Hash_maxLen = 15
|
||||
const _Hash_text = "aboutputargetbodyaccept-charsetdatalistarticlearbackgroundet" +
|
||||
"erminatemplatembedatatypeofaceaddressamp-boilerplatealinkbdi" +
|
||||
"reversedefaultMutedefaultSelectedeferowspanamedialogasyncanv" +
|
||||
"asideclarelabelongdescrollingaudiobjectfooterubyautofocusema" +
|
||||
"paramainertcitemscopedelautoplayaxismapauseonexitimenubgcolo" +
|
||||
"rulesectionoresizeblockquotebuttonoscriptitleclassidetailsel" +
|
||||
"ectedfnoshadefaultCheckedisabledivaluetypecodebaseamlesshape" +
|
||||
"codetypemustmatcheckedlcolgroupicturesourcecolspanowraplaint" +
|
||||
"extareadonlycompactrackeygenctypecontrolslotranslatefieldset" +
|
||||
"figcaptionfigurequiredtruespeedformactionformnovalidateh2h3h" +
|
||||
"4h5h6hgrouposterhiddenabledhtmlhttp-equivaricontentiframebor" +
|
||||
"derimginlistronginputinsmallowfullscreenmanifestylegendmarkm" +
|
||||
"atheadermaxlength1metabindexmlnsortablemetermethodmultiplemu" +
|
||||
"tedoptionpropertysubdopenavalignohreflanguagesummarysuprogre" +
|
||||
"ssrcsetsvgvideoptgrouprefixmprofilevisiblevlinkvocabbrwbr"
|
||||
"erminatemplateacronymainertcanvasideclarelabelongdescrolling" +
|
||||
"addressamp-boilerplateasynciteaudiobjectfootereversedatatype" +
|
||||
"ofacenterowspanamedialogautofocusemappletimenuitemscopedefau" +
|
||||
"ltMutedefaultSelectedeferubyautoplayaxismaparamanifestrongbg" +
|
||||
"colorbigblockquotebuttonoframesetitleclassidetailselectedfno" +
|
||||
"hreflanguagecodebasefontrackbdircodetypemustmatcheckedisable" +
|
||||
"divaluetypecolgroupauseonexitranslatecolspanoresizecompactru" +
|
||||
"espeedlcontrolshapefieldsetfigcaptionoscriptfigurequiredtfor" +
|
||||
"mactionoshadefaultCheckedformnovalidateh2h3h4h5h6hgroupictur" +
|
||||
"esourcehiddenabledhtmlhttp-equivaricontentiframeborderimagei" +
|
||||
"mginlistrikeygenctypeinputinslotmarkmarqueematheadermaxlengt" +
|
||||
"h1metabindexmlnsmallowfullscreenowraplaintextareadonlymeterm" +
|
||||
"ethodmultiplegendmutedoptionprofileprogressortablepropertysr" +
|
||||
"csetstylesubdopenavalignobruleseamlessectionoembedelsummarys" +
|
||||
"uprefixmpostersvgvideoptgrouportalinkvisiblevlinkvocabbrwbr"
|
||||
|
||||
var _Hash_table = [1 << 9]Hash{
|
||||
0x0: 0x1df08, // textarea
|
||||
0x4: 0x32d02, // hr
|
||||
0x8: 0x1c207, // picture
|
||||
0xb: 0x18905, // value
|
||||
0xf: 0x2e408, // tabindex
|
||||
0x12: 0x15905, // class
|
||||
0x15: 0x37e03, // wbr
|
||||
0x18: 0x1a80d, // typemustmatch
|
||||
0x1a: 0x1b902, // dl
|
||||
0x1d: 0xf903, // rtc
|
||||
0x1e: 0x25702, // h4
|
||||
0x22: 0x2ef08, // sortable
|
||||
0x24: 0x4208, // template
|
||||
0x25: 0x28c0b, // frameborder
|
||||
0x28: 0x37a04, // abbr
|
||||
0x29: 0x28b06, // iframe
|
||||
0x2a: 0x610f, // amp-boilerplate
|
||||
0x2c: 0x1e408, // readonly
|
||||
0x30: 0x23f06, // action
|
||||
0x33: 0x28c05, // frame
|
||||
0x35: 0x12c05, // rules
|
||||
0x36: 0x30208, // multiple
|
||||
0x38: 0x31f03, // bdo
|
||||
0x39: 0x1d506, // nowrap
|
||||
0x3e: 0x21408, // fieldset
|
||||
0x3f: 0x7503, // bdi
|
||||
0x46: 0x7f0c, // defaultMuted
|
||||
0x49: 0x35205, // video
|
||||
0x4c: 0x19808, // seamless
|
||||
0x4d: 0x13608, // noresize
|
||||
0x4f: 0xb602, // id
|
||||
0x51: 0x25d06, // hgroup
|
||||
0x52: 0x23102, // dt
|
||||
0x55: 0x12805, // color
|
||||
0x56: 0x34003, // sup
|
||||
0x59: 0x370d, // undeterminate
|
||||
0x5a: 0x35608, // optgroup
|
||||
0x5b: 0x2d206, // header
|
||||
0x5c: 0xb405, // aside
|
||||
0x5f: 0x10005, // scope
|
||||
0x60: 0x101, // b
|
||||
0x61: 0xcb02, // ol
|
||||
0x64: 0x32b06, // nohref
|
||||
0x65: 0x1da09, // plaintext
|
||||
0x66: 0x20804, // slot
|
||||
0x67: 0x11004, // axis
|
||||
0x68: 0x12803, // col
|
||||
0x69: 0x32606, // valign
|
||||
0x6c: 0x2d105, // thead
|
||||
0x70: 0x34906, // srcset
|
||||
0x71: 0x26806, // hidden
|
||||
0x76: 0x1bb08, // colgroup
|
||||
0x78: 0x34f03, // svg
|
||||
0x7b: 0x2cb04, // mark
|
||||
0x7e: 0x33104, // lang
|
||||
0x81: 0x1cf04, // cols
|
||||
0x86: 0x5a07, // address
|
||||
0x8b: 0xf404, // main
|
||||
0x8c: 0x4302, // em
|
||||
0x8f: 0x32d08, // hreflang
|
||||
0x93: 0x1b307, // checked
|
||||
0x94: 0x25902, // h5
|
||||
0x95: 0x301, // u
|
||||
0x96: 0x32705, // align
|
||||
0x97: 0x14301, // q
|
||||
0x99: 0xd506, // object
|
||||
0x9b: 0x28407, // content
|
||||
0x9d: 0xc809, // scrolling
|
||||
0x9f: 0x36407, // profile
|
||||
0xa0: 0x34903, // src
|
||||
0xa1: 0xda05, // tfoot
|
||||
0xa3: 0x2f705, // meter
|
||||
0xa4: 0x37705, // vocab
|
||||
0xa6: 0xd04, // body
|
||||
0xa8: 0x19204, // code
|
||||
0xac: 0x20108, // controls
|
||||
0xb0: 0x2ab05, // small
|
||||
0xb1: 0x18008, // disabled
|
||||
0xb5: 0x5604, // face
|
||||
0xb6: 0x501, // p
|
||||
0xb9: 0x2302, // li
|
||||
0xbb: 0xe409, // autofocus
|
||||
0xbf: 0x27304, // html
|
||||
0xc2: 0x4d08, // datatype
|
||||
0xc6: 0x35d06, // prefix
|
||||
0xcb: 0x35d03, // pre
|
||||
0xcc: 0x1106, // accept
|
||||
0xd1: 0x23b03, // for
|
||||
0xd5: 0x29e06, // strong
|
||||
0xd6: 0x9c07, // rowspan
|
||||
0xd7: 0x25502, // h3
|
||||
0xd8: 0x2cf04, // math
|
||||
0xde: 0x16e07, // noshade
|
||||
0xdf: 0x19f05, // shape
|
||||
0xe1: 0x10006, // scoped
|
||||
0xe3: 0x706, // target
|
||||
0xe6: 0x21c0a, // figcaption
|
||||
0xe9: 0x1df04, // text
|
||||
0xea: 0x1c708, // resource
|
||||
0xec: 0xee03, // map
|
||||
0xf0: 0x29a06, // inlist
|
||||
0xf1: 0x16506, // select
|
||||
0xf2: 0x1f606, // keygen
|
||||
0xf3: 0x5106, // typeof
|
||||
0xf6: 0xb006, // canvas
|
||||
0xf7: 0x30f06, // option
|
||||
0xf8: 0xbe05, // label
|
||||
0xf9: 0xbc03, // rel
|
||||
0xfb: 0x1f04, // data
|
||||
0xfd: 0x6004, // samp
|
||||
0x100: 0x110e, // accept-charset
|
||||
0x101: 0xeb06, // usemap
|
||||
0x103: 0x2bc08, // manifest
|
||||
0x109: 0xa204, // name
|
||||
0x10a: 0x14806, // button
|
||||
0x10b: 0x2b05, // clear
|
||||
0x10e: 0x33907, // summary
|
||||
0x10f: 0x2e204, // meta
|
||||
0x110: 0x33108, // language
|
||||
0x112: 0x300a, // background
|
||||
0x113: 0x2707, // article
|
||||
0x116: 0x23b0a, // formaction
|
||||
0x119: 0x1, // a
|
||||
0x11b: 0x5, // about
|
||||
0x11c: 0xfc09, // itemscope
|
||||
0x11e: 0x14d08, // noscript
|
||||
0x11f: 0x15907, // classid
|
||||
0x120: 0x36203, // xmp
|
||||
0x121: 0x19604, // base
|
||||
0x123: 0x1c01, // s
|
||||
0x124: 0x36b07, // visible
|
||||
0x126: 0x37b02, // bb
|
||||
0x127: 0x9c04, // rows
|
||||
0x12d: 0x2450e, // formnovalidate
|
||||
0x131: 0x1f205, // track
|
||||
0x135: 0x18703, // div
|
||||
0x136: 0xac05, // async
|
||||
0x137: 0x31508, // property
|
||||
0x13a: 0x16c03, // dfn
|
||||
0x13e: 0xf605, // inert
|
||||
0x142: 0x10503, // del
|
||||
0x144: 0x25302, // h2
|
||||
0x147: 0x2c205, // style
|
||||
0x149: 0x29703, // img
|
||||
0x14a: 0xc05, // tbody
|
||||
0x14b: 0x7603, // dir
|
||||
0x14c: 0x2eb05, // xmlns
|
||||
0x14e: 0x1f08, // datalist
|
||||
0x14f: 0x32d04, // href
|
||||
0x150: 0x1f202, // tr
|
||||
0x151: 0x13e0a, // blockquote
|
||||
0x152: 0x18909, // valuetype
|
||||
0x155: 0xdb06, // footer
|
||||
0x157: 0x14f06, // script
|
||||
0x158: 0x1cf07, // colspan
|
||||
0x15d: 0x1730e, // defaultChecked
|
||||
0x15f: 0x2490a, // novalidate
|
||||
0x164: 0x1a408, // codetype
|
||||
0x165: 0x2c506, // legend
|
||||
0x16b: 0x1160b, // pauseonexit
|
||||
0x16c: 0x21f07, // caption
|
||||
0x16f: 0x26c07, // enabled
|
||||
0x173: 0x26206, // poster
|
||||
0x175: 0x30a05, // muted
|
||||
0x176: 0x11205, // ismap
|
||||
0x178: 0x2a903, // ins
|
||||
0x17a: 0xe004, // ruby
|
||||
0x17b: 0x37c02, // br
|
||||
0x17c: 0x8a0f, // defaultSelected
|
||||
0x17d: 0x7403, // kbd
|
||||
0x17f: 0x1c906, // source
|
||||
0x182: 0x9f04, // span
|
||||
0x184: 0x2d803, // max
|
||||
0x18a: 0x5b02, // dd
|
||||
0x18b: 0x13a04, // size
|
||||
0x18c: 0xa405, // media
|
||||
0x18d: 0x19208, // codebase
|
||||
0x18f: 0x4905, // embed
|
||||
0x192: 0x5104, // type
|
||||
0x193: 0xf005, // param
|
||||
0x194: 0x25b02, // h6
|
||||
0x197: 0x28304, // icon
|
||||
0x198: 0x12607, // bgcolor
|
||||
0x199: 0x2ad0f, // allowfullscreen
|
||||
0x19a: 0x12004, // time
|
||||
0x19b: 0x7803, // rev
|
||||
0x19d: 0x34208, // progress
|
||||
0x19e: 0x22606, // figure
|
||||
0x1a0: 0x6a02, // rp
|
||||
0x1a2: 0xa606, // dialog
|
||||
0x1a4: 0x2802, // rt
|
||||
0x1a7: 0x1e304, // area
|
||||
0x1a8: 0x7808, // reversed
|
||||
0x1aa: 0x32104, // open
|
||||
0x1ac: 0x2d204, // head
|
||||
0x1ad: 0x7005, // alink
|
||||
0x1af: 0x28003, // var
|
||||
0x1b0: 0x15f07, // details
|
||||
0x1b1: 0x2401, // i
|
||||
0x1b3: 0x1e02, // td
|
||||
0x1b4: 0xb707, // declare
|
||||
0x1b5: 0x8302, // ul
|
||||
0x1ba: 0x2fc06, // method
|
||||
0x1bd: 0x13007, // section
|
||||
0x1be: 0x22a08, // required
|
||||
0x1c2: 0x9805, // defer
|
||||
0x1c3: 0x37205, // vlink
|
||||
0x1c4: 0x15405, // title
|
||||
0x1c5: 0x2770a, // http-equiv
|
||||
0x1c6: 0x1fa07, // enctype
|
||||
0x1c7: 0x1ec07, // compact
|
||||
0x1c8: 0x2d809, // maxlength
|
||||
0x1c9: 0x16508, // selected
|
||||
0x1cc: 0xd105, // audio
|
||||
0x1cd: 0xc208, // longdesc
|
||||
0x1d1: 0xfb04, // cite
|
||||
0x1da: 0x2505, // start
|
||||
0x1de: 0x2d102, // th
|
||||
0x1df: 0x10808, // autoplay
|
||||
0x1e2: 0x7104, // link
|
||||
0x1e3: 0x206, // output
|
||||
0x1e5: 0x12204, // menu
|
||||
0x1e6: 0x2a405, // input
|
||||
0x1eb: 0x32403, // nav
|
||||
0x1ec: 0x31d03, // sub
|
||||
0x1ee: 0x1807, // charset
|
||||
0x1ef: 0x7f07, // default
|
||||
0x1f3: 0x2f205, // table
|
||||
0x1f4: 0x23b04, // form
|
||||
0x1f5: 0x23209, // truespeed
|
||||
0x1f6: 0x2f02, // rb
|
||||
0x1fb: 0x20b09, // translate
|
||||
0x1fd: 0x2e002, // h1
|
||||
0x1: 0x13e06, // button
|
||||
0x3: 0x2a207, // enctype
|
||||
0x4: 0x32206, // option
|
||||
0x5: 0x1fb0a, // figcaption
|
||||
0x7: 0x2ae03, // ins
|
||||
0x9: 0x9605, // audio
|
||||
0xb: 0x2830b, // frameborder
|
||||
0xd: 0x2190a, // formaction
|
||||
0xe: 0x5, // about
|
||||
0xf: 0x34606, // srcset
|
||||
0x10: 0x1dd09, // truespeed
|
||||
0x11: 0xeb0c, // defaultMuted
|
||||
0x13: 0xa006, // footer
|
||||
0x15: 0x19d08, // disabled
|
||||
0x16: 0x26e0a, // http-equiv
|
||||
0x19: 0x3a504, // link
|
||||
0x1a: 0x29606, // inlist
|
||||
0x1d: 0x10804, // ruby
|
||||
0x21: 0x2a905, // input
|
||||
0x22: 0x35803, // nav
|
||||
0x25: 0x7902, // dd
|
||||
0x26: 0x2350e, // formnovalidate
|
||||
0x28: 0x16804, // href
|
||||
0x29: 0x24702, // h4
|
||||
0x2b: 0x10405, // defer
|
||||
0x2d: 0x1f308, // fieldset
|
||||
0x2e: 0xeb07, // default
|
||||
0x34: 0x2fd04, // area
|
||||
0x36: 0xb006, // typeof
|
||||
0x37: 0x37307, // noembed
|
||||
0x38: 0x5e07, // declare
|
||||
0x3a: 0x4a07, // acronym
|
||||
0x3b: 0xc05, // tbody
|
||||
0x3e: 0x15107, // classid
|
||||
0x41: 0x9a06, // object
|
||||
0x43: 0x16403, // dfn
|
||||
0x44: 0xef02, // ul
|
||||
0x45: 0x16c04, // lang
|
||||
0x47: 0x16606, // nohref
|
||||
0x49: 0x2c803, // max
|
||||
0x4a: 0x6505, // label
|
||||
0x4c: 0x1d304, // size
|
||||
0x4d: 0xe606, // scoped
|
||||
0x4f: 0x15105, // class
|
||||
0x50: 0x11404, // axis
|
||||
0x54: 0xbf04, // span
|
||||
0x56: 0x19707, // checked
|
||||
0x59: 0x38506, // prefix
|
||||
0x5b: 0x4208, // template
|
||||
0x5c: 0x370d, // undeterminate
|
||||
0x5d: 0xc606, // dialog
|
||||
0x5e: 0x6908, // longdesc
|
||||
0x60: 0x21903, // for
|
||||
0x61: 0x2c102, // th
|
||||
0x64: 0x15d08, // selected
|
||||
0x65: 0x35103, // sub
|
||||
0x6a: 0xd306, // usemap
|
||||
0x6e: 0x24d06, // hgroup
|
||||
0x6f: 0x38303, // sup
|
||||
0x70: 0x2b404, // mark
|
||||
0x71: 0x28206, // iframe
|
||||
0x72: 0x30605, // meter
|
||||
0x74: 0x21008, // required
|
||||
0x75: 0x1f04, // data
|
||||
0x78: 0x14308, // noframes
|
||||
0x83: 0x7807, // address
|
||||
0x88: 0x10c08, // autoplay
|
||||
0x8a: 0x28e05, // image
|
||||
0x8b: 0x16c08, // language
|
||||
0x8e: 0x2f904, // text
|
||||
0x8f: 0x16802, // hr
|
||||
0x90: 0x5d02, // id
|
||||
0x92: 0x31108, // multiple
|
||||
0x94: 0x16808, // hreflang
|
||||
0x95: 0x2db05, // xmlns
|
||||
0x96: 0x24902, // h5
|
||||
0x98: 0x25207, // picture
|
||||
0x99: 0x1106, // accept
|
||||
0x9a: 0x1a609, // valuetype
|
||||
0x9b: 0x3a006, // portal
|
||||
0x9d: 0xac08, // datatype
|
||||
0x9e: 0x18403, // bdi
|
||||
0xa0: 0x27a04, // icon
|
||||
0xa2: 0xa503, // rev
|
||||
0xa5: 0x25708, // resource
|
||||
0xa8: 0x35504, // open
|
||||
0xac: 0x4302, // em
|
||||
0xae: 0x1340a, // blockquote
|
||||
0xb0: 0x2f409, // plaintext
|
||||
0xb1: 0x2d204, // meta
|
||||
0xb2: 0x1c01, // s
|
||||
0xb4: 0xdc04, // time
|
||||
0xb5: 0x1fe07, // caption
|
||||
0xb8: 0x33e08, // property
|
||||
0xb9: 0x1, // a
|
||||
0xbb: 0x2b807, // marquee
|
||||
0xbc: 0x3b505, // vocab
|
||||
0xbd: 0x1e502, // dl
|
||||
0xbf: 0xbc07, // rowspan
|
||||
0xc4: 0x18503, // dir
|
||||
0xc5: 0x39908, // optgroup
|
||||
0xcc: 0x38c06, // poster
|
||||
0xcd: 0x24502, // h3
|
||||
0xce: 0x3b804, // abbr
|
||||
0xd1: 0x17408, // codebase
|
||||
0xd2: 0x27b07, // content
|
||||
0xd4: 0x7e04, // samp
|
||||
0xd6: 0xc204, // name
|
||||
0xd9: 0x14c05, // title
|
||||
0xda: 0x1a605, // value
|
||||
0xdd: 0xb004, // type
|
||||
0xde: 0x35f04, // nobr
|
||||
0xe0: 0x17c04, // font
|
||||
0xe1: 0xd603, // map
|
||||
0xe2: 0x2d002, // h1
|
||||
0xe3: 0x22207, // noshade
|
||||
0xe4: 0x6303, // rel
|
||||
0xe5: 0x14002, // tt
|
||||
0xe7: 0xde04, // menu
|
||||
0xeb: 0x2f908, // textarea
|
||||
0xee: 0x35b05, // align
|
||||
0xf1: 0x29303, // img
|
||||
0xf2: 0x35a06, // valign
|
||||
0xf3: 0x2c204, // head
|
||||
0xf4: 0x12a07, // bgcolor
|
||||
0xf5: 0x5004, // main
|
||||
0xf6: 0x2302, // li
|
||||
0xf7: 0x5205, // inert
|
||||
0xfa: 0x5706, // canvas
|
||||
0xfb: 0xe605, // scope
|
||||
0xfc: 0x15d06, // select
|
||||
0x100: 0xa508, // reversed
|
||||
0x101: 0x20408, // noscript
|
||||
0x102: 0x37c07, // summary
|
||||
0x103: 0x24b02, // h6
|
||||
0x106: 0x17404, // code
|
||||
0x107: 0x14508, // frameset
|
||||
0x10a: 0x12406, // strong
|
||||
0x10d: 0x300a, // background
|
||||
0x10e: 0x18303, // kbd
|
||||
0x114: 0x31706, // legend
|
||||
0x116: 0x32f08, // progress
|
||||
0x118: 0x2d408, // tabindex
|
||||
0x119: 0x34603, // src
|
||||
0x11c: 0x39505, // video
|
||||
0x11f: 0x29a06, // strike
|
||||
0x121: 0xd706, // applet
|
||||
0x123: 0x2802, // rt
|
||||
0x125: 0x20606, // script
|
||||
0x128: 0xbc04, // rows
|
||||
0x129: 0x2707, // article
|
||||
0x12e: 0x9204, // cite
|
||||
0x131: 0x18c0d, // typemustmatch
|
||||
0x133: 0x17f05, // track
|
||||
0x135: 0x3b902, // bb
|
||||
0x136: 0x1ee05, // shape
|
||||
0x137: 0x5b05, // aside
|
||||
0x138: 0x1b60b, // pauseonexit
|
||||
0x13c: 0x38503, // pre
|
||||
0x140: 0x301, // u
|
||||
0x149: 0x1a403, // div
|
||||
0x14c: 0x3a405, // alink
|
||||
0x14e: 0x27703, // var
|
||||
0x14f: 0x21d06, // action
|
||||
0x152: 0x2b05, // clear
|
||||
0x154: 0x2401, // i
|
||||
0x155: 0x21702, // dt
|
||||
0x156: 0x36608, // seamless
|
||||
0x157: 0x21904, // form
|
||||
0x15b: 0x15707, // details
|
||||
0x15f: 0x8e05, // async
|
||||
0x160: 0x26a04, // html
|
||||
0x161: 0x33608, // sortable
|
||||
0x165: 0x2f02, // rb
|
||||
0x167: 0x2e10f, // allowfullscreen
|
||||
0x168: 0x17804, // base
|
||||
0x169: 0x25f06, // hidden
|
||||
0x16e: 0x2ef06, // nowrap
|
||||
0x16f: 0x2505, // start
|
||||
0x170: 0x14505, // frame
|
||||
0x171: 0x1f08, // datalist
|
||||
0x173: 0x12502, // tr
|
||||
0x174: 0x30b06, // method
|
||||
0x175: 0x101, // b
|
||||
0x176: 0x1c904, // cols
|
||||
0x178: 0x110e, // accept-charset
|
||||
0x17a: 0x36205, // rules
|
||||
0x17b: 0x7f0f, // amp-boilerplate
|
||||
0x17f: 0x2270e, // defaultChecked
|
||||
0x180: 0x32807, // profile
|
||||
0x181: 0x2b004, // slot
|
||||
0x182: 0x11a05, // param
|
||||
0x185: 0x1c907, // colspan
|
||||
0x186: 0x34c05, // style
|
||||
0x187: 0x1e02, // td
|
||||
0x188: 0x12c05, // color
|
||||
0x18c: 0x13901, // q
|
||||
0x18d: 0x3b005, // vlink
|
||||
0x18e: 0x39203, // svg
|
||||
0x18f: 0x33905, // table
|
||||
0x190: 0x29e06, // keygen
|
||||
0x192: 0x20c06, // figure
|
||||
0x193: 0x3a907, // visible
|
||||
0x195: 0x17808, // basefont
|
||||
0x196: 0x8802, // rp
|
||||
0x197: 0xf60f, // defaultSelected
|
||||
0x198: 0x1af08, // colgroup
|
||||
0x19a: 0x3bc03, // wbr
|
||||
0x19c: 0x36d07, // section
|
||||
0x19d: 0x25906, // source
|
||||
0x19f: 0x2bf04, // math
|
||||
0x1a1: 0x2fe08, // readonly
|
||||
0x1a7: 0x1e708, // controls
|
||||
0x1a9: 0xde08, // menuitem
|
||||
0x1ad: 0x206, // output
|
||||
0x1b0: 0x2c809, // maxlength
|
||||
0x1b2: 0xe209, // itemscope
|
||||
0x1b9: 0x501, // p
|
||||
0x1bc: 0x2df05, // small
|
||||
0x1bd: 0x36102, // br
|
||||
0x1c0: 0x5503, // rtc
|
||||
0x1c1: 0x1c009, // translate
|
||||
0x1c4: 0x35303, // bdo
|
||||
0x1c5: 0xd04, // body
|
||||
0x1c8: 0xb706, // center
|
||||
0x1c9: 0x2c105, // thead
|
||||
0x1ca: 0xcc09, // autofocus
|
||||
0x1cc: 0xb504, // face
|
||||
0x1cd: 0x24302, // h2
|
||||
0x1ce: 0x11e08, // manifest
|
||||
0x1d0: 0x706, // target
|
||||
0x1d1: 0x11605, // ismap
|
||||
0x1d3: 0xc405, // media
|
||||
0x1d7: 0x13103, // big
|
||||
0x1da: 0x37903, // del
|
||||
0x1dc: 0x6f09, // scrolling
|
||||
0x1de: 0x37505, // embed
|
||||
0x1e0: 0x31d05, // muted
|
||||
0x1e4: 0x2390a, // novalidate
|
||||
0x1e6: 0x7202, // ol
|
||||
0x1eb: 0x9f05, // tfoot
|
||||
0x1ec: 0x18808, // codetype
|
||||
0x1ee: 0x26307, // enabled
|
||||
0x1f0: 0x2c206, // header
|
||||
0x1f1: 0x1cf08, // noresize
|
||||
0x1f6: 0x1d707, // compact
|
||||
0x1f9: 0x12c03, // col
|
||||
0x1fa: 0x38a03, // xmp
|
||||
0x1fb: 0x1807, // charset
|
||||
}
|
||||
|
|
41
vendor/github.com/tdewolff/minify/v2/html/html.go
generated
vendored
41
vendor/github.com/tdewolff/minify/v2/html/html.go
generated
vendored
|
@ -129,7 +129,7 @@ func (o *Minifier) Minify(m *minify.M, w io.Writer, r io.Reader, _ map[string]st
|
|||
var params map[string]string
|
||||
if rawTagHash == Iframe {
|
||||
mimetype = htmlMimeBytes
|
||||
} else if len(rawTagMediatype) > 0 {
|
||||
} else if 0 < len(rawTagMediatype) {
|
||||
mimetype, params = parse.Mediatype(rawTagMediatype)
|
||||
} else if rawTagHash == Script {
|
||||
mimetype = jsMimeBytes
|
||||
|
@ -169,20 +169,15 @@ func (o *Minifier) Minify(m *minify.M, w io.Writer, r io.Reader, _ map[string]st
|
|||
t.Data = t.Data[:len(t.Data)-1]
|
||||
omitSpace = false
|
||||
break
|
||||
} else if next.TokenType == html.TextToken {
|
||||
// this only happens when a comment, doctype or phrasing end tag (only for !o.KeepWhitespace) was in between
|
||||
// remove if the text token starts with a whitespace
|
||||
if len(next.Data) > 0 && parse.IsWhitespace(next.Data[0]) {
|
||||
t.Data = t.Data[:len(t.Data)-1]
|
||||
omitSpace = false
|
||||
}
|
||||
} else if next.TokenType == html.TextToken && !parse.IsAllWhitespace(next.Data) {
|
||||
// stop looking when text encountered
|
||||
break
|
||||
} else if next.TokenType == html.StartTagToken || next.TokenType == html.EndTagToken {
|
||||
if o.KeepWhitespace {
|
||||
break
|
||||
}
|
||||
// remove when followed up by a block tag
|
||||
if next.Traits&nonPhrasingTag != 0 {
|
||||
// remove when followed by a block tag
|
||||
if next.Traits&blockTag != 0 {
|
||||
t.Data = t.Data[:len(t.Data)-1]
|
||||
omitSpace = false
|
||||
break
|
||||
|
@ -271,14 +266,14 @@ func (o *Minifier) Minify(m *minify.M, w io.Writer, r io.Reader, _ map[string]st
|
|||
}
|
||||
}
|
||||
|
||||
if t.Traits&nonPhrasingTag != 0 {
|
||||
omitSpace = true // omit spaces after block elements
|
||||
} else if o.KeepWhitespace || t.Traits&objectTag != 0 {
|
||||
omitSpace = false
|
||||
}
|
||||
|
||||
if !omitEndTag {
|
||||
if len(t.Data) > 3+len(t.Text) {
|
||||
if o.KeepWhitespace || t.Traits&objectTag != 0 {
|
||||
omitSpace = false
|
||||
} else if t.Traits&blockTag != 0 {
|
||||
omitSpace = true // omit spaces after block elements
|
||||
}
|
||||
|
||||
if 3+len(t.Text) < len(t.Data) {
|
||||
t.Data[2+len(t.Text)] = '>'
|
||||
t.Data = t.Data[:3+len(t.Text)]
|
||||
}
|
||||
|
@ -296,7 +291,7 @@ func (o *Minifier) Minify(m *minify.M, w io.Writer, r io.Reader, _ map[string]st
|
|||
|
||||
if o.KeepWhitespace || t.Traits&objectTag != 0 {
|
||||
omitSpace = false
|
||||
} else if t.Traits&nonPhrasingTag != 0 {
|
||||
} else if t.Traits&blockTag != 0 {
|
||||
omitSpace = true // omit spaces after block elements
|
||||
}
|
||||
|
||||
|
@ -327,7 +322,7 @@ func (o *Minifier) Minify(m *minify.M, w io.Writer, r io.Reader, _ map[string]st
|
|||
for i := 0; i < len(content.AttrVal); i++ {
|
||||
if content.AttrVal[i] == '=' && i+2 < len(content.AttrVal) {
|
||||
i++
|
||||
if n := parse.Number(content.AttrVal[i:]); n > 0 {
|
||||
if n := parse.Number(content.AttrVal[i:]); 0 < n {
|
||||
minNum := minify.Number(content.AttrVal[i:i+n], -1)
|
||||
if len(minNum) < n {
|
||||
copy(content.AttrVal[i:i+len(minNum)], minNum)
|
||||
|
@ -434,10 +429,10 @@ func (o *Minifier) Minify(m *minify.M, w io.Writer, r io.Reader, _ map[string]st
|
|||
if len(val) == 0 {
|
||||
continue
|
||||
}
|
||||
} else if len(attr.Text) > 2 && attr.Text[0] == 'o' && attr.Text[1] == 'n' {
|
||||
} else if 2 < len(attr.Text) && attr.Text[0] == 'o' && attr.Text[1] == 'n' {
|
||||
// JS minifier for attribute inline code
|
||||
val = parse.TrimWhitespace(val)
|
||||
if len(val) >= 11 && parse.EqualFold(val[:11], jsSchemeBytes) {
|
||||
if 11 <= len(val) && parse.EqualFold(val[:11], jsSchemeBytes) {
|
||||
val = val[11:]
|
||||
}
|
||||
attrMinifyBuffer.Reset()
|
||||
|
@ -475,7 +470,7 @@ func (o *Minifier) Minify(m *minify.M, w io.Writer, r io.Reader, _ map[string]st
|
|||
|
||||
w.Write(spaceBytes)
|
||||
w.Write(attr.Text)
|
||||
if len(val) > 0 && attr.Traits&booleanAttr == 0 {
|
||||
if 0 < len(val) && attr.Traits&booleanAttr == 0 {
|
||||
w.Write(isBytes)
|
||||
|
||||
// use double quotes for RDFa attributes
|
||||
|
@ -504,7 +499,7 @@ func (o *Minifier) Minify(m *minify.M, w io.Writer, r io.Reader, _ map[string]st
|
|||
}
|
||||
|
||||
// keep space after phrasing tags (<i>, <span>, ...) FontAwesome etc.
|
||||
if t.TokenType == html.StartTagToken && t.Traits&nonPhrasingTag == 0 {
|
||||
if t.TokenType == html.StartTagToken && t.Traits == normalTag {
|
||||
if next := tb.Peek(0); next.Hash == t.Hash && next.TokenType == html.EndTagToken {
|
||||
omitSpace = false
|
||||
}
|
||||
|
|
158
vendor/github.com/tdewolff/minify/v2/html/table.go
generated
vendored
158
vendor/github.com/tdewolff/minify/v2/html/table.go
generated
vendored
|
@ -3,12 +3,12 @@ package html
|
|||
type traits uint16
|
||||
|
||||
const (
|
||||
normalTag traits = 1 << iota
|
||||
rawTag // raw tags need special processing for their content
|
||||
nonPhrasingTag // non-phrasing elements are unaffected by whitespace, remove spaces around these tags
|
||||
objectTag // content tags with a few exclusions, keep spaces after these open/close tags
|
||||
omitPTag // omit p end tag if it is followed by this start tag
|
||||
keepPTag // keep p end tag if it is followed by this end tag
|
||||
normalTag traits = 1 << iota
|
||||
rawTag // raw tags need special processing for their content
|
||||
blockTag // remove spaces around these tags
|
||||
objectTag // keep spaces after these open/close tags
|
||||
omitPTag // omit p end tag if it is followed by this start tag
|
||||
keepPTag // keep p end tag if it is followed by this end tag
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -21,54 +21,54 @@ const (
|
|||
var tagMap = map[Hash]traits{
|
||||
A: keepPTag,
|
||||
Abbr: normalTag,
|
||||
Address: nonPhrasingTag | omitPTag,
|
||||
Address: blockTag | omitPTag,
|
||||
Area: normalTag,
|
||||
Article: nonPhrasingTag | omitPTag,
|
||||
Aside: nonPhrasingTag | omitPTag,
|
||||
Article: blockTag | omitPTag,
|
||||
Aside: blockTag | omitPTag,
|
||||
Audio: keepPTag,
|
||||
B: normalTag,
|
||||
Base: normalTag,
|
||||
Bb: normalTag,
|
||||
Bdi: normalTag,
|
||||
Bdo: normalTag,
|
||||
Blockquote: nonPhrasingTag | omitPTag,
|
||||
Body: nonPhrasingTag,
|
||||
Br: nonPhrasingTag,
|
||||
Blockquote: blockTag | omitPTag,
|
||||
Body: normalTag,
|
||||
Br: blockTag,
|
||||
Button: objectTag,
|
||||
Canvas: objectTag | keepPTag,
|
||||
Caption: nonPhrasingTag,
|
||||
Caption: blockTag,
|
||||
Cite: normalTag,
|
||||
Code: normalTag,
|
||||
Col: nonPhrasingTag,
|
||||
Colgroup: nonPhrasingTag,
|
||||
Col: blockTag,
|
||||
Colgroup: blockTag,
|
||||
Data: normalTag,
|
||||
Datalist: normalTag,
|
||||
Dd: nonPhrasingTag,
|
||||
Datalist: normalTag, // no text content
|
||||
Dd: blockTag,
|
||||
Del: keepPTag,
|
||||
Details: omitPTag,
|
||||
Details: blockTag | omitPTag,
|
||||
Dfn: normalTag,
|
||||
Dialog: normalTag,
|
||||
Div: nonPhrasingTag | omitPTag,
|
||||
Dl: nonPhrasingTag | omitPTag,
|
||||
Dt: nonPhrasingTag,
|
||||
Div: blockTag | omitPTag,
|
||||
Dl: blockTag | omitPTag,
|
||||
Dt: blockTag,
|
||||
Em: normalTag,
|
||||
Embed: nonPhrasingTag,
|
||||
Fieldset: nonPhrasingTag | omitPTag,
|
||||
Figcaption: nonPhrasingTag | omitPTag,
|
||||
Figure: nonPhrasingTag | omitPTag,
|
||||
Footer: nonPhrasingTag | omitPTag,
|
||||
Form: nonPhrasingTag | omitPTag,
|
||||
H1: nonPhrasingTag | omitPTag,
|
||||
H2: nonPhrasingTag | omitPTag,
|
||||
H3: nonPhrasingTag | omitPTag,
|
||||
H4: nonPhrasingTag | omitPTag,
|
||||
H5: nonPhrasingTag | omitPTag,
|
||||
H6: nonPhrasingTag | omitPTag,
|
||||
Head: nonPhrasingTag,
|
||||
Header: nonPhrasingTag | omitPTag,
|
||||
Hgroup: nonPhrasingTag,
|
||||
Hr: nonPhrasingTag | omitPTag,
|
||||
Html: nonPhrasingTag,
|
||||
Embed: normalTag,
|
||||
Fieldset: blockTag | omitPTag,
|
||||
Figcaption: blockTag | omitPTag,
|
||||
Figure: blockTag | omitPTag,
|
||||
Footer: blockTag | omitPTag,
|
||||
Form: blockTag | omitPTag,
|
||||
H1: blockTag | omitPTag,
|
||||
H2: blockTag | omitPTag,
|
||||
H3: blockTag | omitPTag,
|
||||
H4: blockTag | omitPTag,
|
||||
H5: blockTag | omitPTag,
|
||||
H6: blockTag | omitPTag,
|
||||
Head: blockTag,
|
||||
Header: blockTag | omitPTag,
|
||||
Hgroup: blockTag,
|
||||
Hr: blockTag | omitPTag,
|
||||
Html: blockTag,
|
||||
I: normalTag,
|
||||
Iframe: rawTag | objectTag,
|
||||
Img: objectTag,
|
||||
|
@ -76,64 +76,90 @@ var tagMap = map[Hash]traits{
|
|||
Ins: keepPTag,
|
||||
Kbd: normalTag,
|
||||
Label: normalTag,
|
||||
Legend: normalTag,
|
||||
Li: nonPhrasingTag,
|
||||
Legend: blockTag,
|
||||
Li: blockTag,
|
||||
Link: normalTag,
|
||||
Main: nonPhrasingTag | omitPTag,
|
||||
Main: blockTag | omitPTag,
|
||||
Map: keepPTag,
|
||||
Mark: normalTag,
|
||||
Math: rawTag,
|
||||
Menu: omitPTag,
|
||||
Meta: nonPhrasingTag,
|
||||
Menu: blockTag | omitPTag,
|
||||
Meta: normalTag,
|
||||
Meter: objectTag,
|
||||
Nav: nonPhrasingTag | omitPTag,
|
||||
Noscript: nonPhrasingTag | keepPTag,
|
||||
Nav: blockTag | omitPTag,
|
||||
Noscript: blockTag | keepPTag,
|
||||
Object: objectTag,
|
||||
Ol: nonPhrasingTag | omitPTag,
|
||||
Optgroup: normalTag,
|
||||
Option: normalTag,
|
||||
Output: nonPhrasingTag,
|
||||
P: nonPhrasingTag | omitPTag,
|
||||
Ol: blockTag | omitPTag,
|
||||
Optgroup: normalTag, // no text content
|
||||
Option: blockTag,
|
||||
Output: normalTag,
|
||||
P: blockTag | omitPTag,
|
||||
Param: normalTag,
|
||||
Picture: normalTag,
|
||||
Pre: nonPhrasingTag | omitPTag,
|
||||
Pre: blockTag | omitPTag,
|
||||
Progress: objectTag,
|
||||
Q: objectTag,
|
||||
Rp: normalTag,
|
||||
Rt: normalTag,
|
||||
Rt: objectTag,
|
||||
Ruby: normalTag,
|
||||
S: normalTag,
|
||||
Samp: normalTag,
|
||||
Script: rawTag,
|
||||
Section: nonPhrasingTag | omitPTag,
|
||||
Section: blockTag | omitPTag,
|
||||
Select: objectTag,
|
||||
Slot: normalTag,
|
||||
Small: normalTag,
|
||||
Source: normalTag,
|
||||
Span: normalTag,
|
||||
Strong: normalTag,
|
||||
Style: rawTag | nonPhrasingTag,
|
||||
Style: rawTag | blockTag,
|
||||
Sub: normalTag,
|
||||
Summary: normalTag,
|
||||
Summary: blockTag,
|
||||
Sup: normalTag,
|
||||
Svg: rawTag | objectTag,
|
||||
Table: nonPhrasingTag | omitPTag,
|
||||
Tbody: nonPhrasingTag,
|
||||
Td: nonPhrasingTag,
|
||||
Table: blockTag | omitPTag,
|
||||
Tbody: blockTag,
|
||||
Td: blockTag,
|
||||
Template: normalTag,
|
||||
Textarea: rawTag | objectTag,
|
||||
Tfoot: nonPhrasingTag,
|
||||
Th: nonPhrasingTag,
|
||||
Thead: nonPhrasingTag,
|
||||
Tfoot: blockTag,
|
||||
Th: blockTag,
|
||||
Thead: blockTag,
|
||||
Time: normalTag,
|
||||
Title: nonPhrasingTag,
|
||||
Tr: nonPhrasingTag,
|
||||
Title: normalTag,
|
||||
Tr: blockTag,
|
||||
Track: normalTag,
|
||||
U: normalTag,
|
||||
Ul: nonPhrasingTag | omitPTag,
|
||||
Ul: blockTag | omitPTag,
|
||||
Var: normalTag,
|
||||
Video: objectTag | keepPTag,
|
||||
Wbr: normalTag,
|
||||
Wbr: objectTag,
|
||||
|
||||
// removed tags
|
||||
Acronym: normalTag,
|
||||
Applet: normalTag,
|
||||
Basefont: normalTag,
|
||||
Big: normalTag,
|
||||
Center: blockTag,
|
||||
Dir: blockTag,
|
||||
Font: normalTag,
|
||||
Frame: normalTag,
|
||||
Frameset: normalTag,
|
||||
Image: objectTag,
|
||||
Marquee: blockTag,
|
||||
Menuitem: normalTag,
|
||||
Nobr: normalTag,
|
||||
Noembed: blockTag,
|
||||
Noframes: blockTag,
|
||||
Plaintext: normalTag,
|
||||
Rtc: objectTag,
|
||||
Rb: normalTag,
|
||||
Strike: normalTag,
|
||||
Tt: normalTag,
|
||||
Xmp: blockTag,
|
||||
|
||||
// experimental tags
|
||||
Portal: normalTag,
|
||||
}
|
||||
|
||||
var attrMap = map[Hash]traits{
|
||||
|
@ -574,7 +600,7 @@ var EntitiesMap = map[string][]byte{
|
|||
"SupersetEqual": []byte("⊇"),
|
||||
"Supset": []byte("⋑"),
|
||||
"THORN": []byte("Þ"),
|
||||
"Tab": []byte(" "),
|
||||
"Tab": []byte("\t"),
|
||||
"Tcaron": []byte("Ť"),
|
||||
"Tcedil": []byte("Ţ"),
|
||||
"Therefore": []byte("∴"),
|
||||
|
|
63
vendor/github.com/tdewolff/parse/v2/strconv/decimal.go
generated
vendored
Normal file
63
vendor/github.com/tdewolff/parse/v2/strconv/decimal.go
generated
vendored
Normal file
|
@ -0,0 +1,63 @@
|
|||
package strconv
|
||||
|
||||
import (
|
||||
"math"
|
||||
)
|
||||
|
||||
func ParseDecimal(b []byte) (float64, int) {
|
||||
i := 0
|
||||
start := i
|
||||
dot := -1
|
||||
trunk := -1
|
||||
n := uint64(0)
|
||||
for ; i < len(b); i++ {
|
||||
c := b[i]
|
||||
if '0' <= c && c <= '9' {
|
||||
if trunk == -1 {
|
||||
if math.MaxUint64/10 < n {
|
||||
trunk = i
|
||||
} else {
|
||||
n *= 10
|
||||
n += uint64(c - '0')
|
||||
}
|
||||
}
|
||||
} else if dot == -1 && c == '.' {
|
||||
dot = i
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
if i == start || i == start+1 && dot == start {
|
||||
return 0.0, 0
|
||||
}
|
||||
|
||||
f := float64(n)
|
||||
mantExp := int64(0)
|
||||
if dot != -1 {
|
||||
if trunk == -1 {
|
||||
trunk = i
|
||||
}
|
||||
mantExp = int64(trunk - dot - 1)
|
||||
} else if trunk != -1 {
|
||||
mantExp = int64(trunk - i)
|
||||
}
|
||||
exp := -mantExp
|
||||
|
||||
// copied from strconv/atof.go
|
||||
if exp == 0 {
|
||||
return f, i
|
||||
} else if 0 < exp && exp <= 15+22 { // int * 10^k
|
||||
// If exponent is big but number of digits is not,
|
||||
// can move a few zeros into the integer part.
|
||||
if 22 < exp {
|
||||
f *= float64pow10[exp-22]
|
||||
exp = 22
|
||||
}
|
||||
if -1e15 <= f && f <= 1e15 {
|
||||
return f * float64pow10[exp], i
|
||||
}
|
||||
} else if exp < 0 && -22 <= exp { // int / 10^k
|
||||
return f / float64pow10[-exp], i
|
||||
}
|
||||
return f * math.Pow10(int(-mantExp)), i
|
||||
}
|
30
vendor/github.com/tdewolff/parse/v2/strconv/float.go
generated
vendored
30
vendor/github.com/tdewolff/parse/v2/strconv/float.go
generated
vendored
|
@ -25,9 +25,9 @@ func ParseFloat(b []byte) (float64, int) {
|
|||
n := uint64(0)
|
||||
for ; i < len(b); i++ {
|
||||
c := b[i]
|
||||
if c >= '0' && c <= '9' {
|
||||
if '0' <= c && c <= '9' {
|
||||
if trunk == -1 {
|
||||
if n > math.MaxUint64/10 {
|
||||
if math.MaxUint64/10 < n {
|
||||
trunk = i
|
||||
} else {
|
||||
n *= 10
|
||||
|
@ -62,7 +62,7 @@ func ParseFloat(b []byte) (float64, int) {
|
|||
if i < len(b) && (b[i] == 'e' || b[i] == 'E') {
|
||||
startExp := i
|
||||
i++
|
||||
if e, expLen := ParseInt(b[i:]); expLen > 0 {
|
||||
if e, expLen := ParseInt(b[i:]); 0 < expLen {
|
||||
expExp = e
|
||||
i += expLen
|
||||
} else {
|
||||
|
@ -74,17 +74,17 @@ func ParseFloat(b []byte) (float64, int) {
|
|||
// copied from strconv/atof.go
|
||||
if exp == 0 {
|
||||
return f, i
|
||||
} else if exp > 0 && exp <= 15+22 { // int * 10^k
|
||||
} else if 0 < exp && exp <= 15+22 { // int * 10^k
|
||||
// If exponent is big but number of digits is not,
|
||||
// can move a few zeros into the integer part.
|
||||
if exp > 22 {
|
||||
if 22 < exp {
|
||||
f *= float64pow10[exp-22]
|
||||
exp = 22
|
||||
}
|
||||
if f <= 1e15 && f >= -1e15 {
|
||||
if -1e15 <= f && f <= 1e15 {
|
||||
return f * float64pow10[exp], i
|
||||
}
|
||||
} else if exp < 0 && exp >= -22 { // int / 10^k
|
||||
} else if -22 <= exp && exp < 0 { // int / 10^k
|
||||
return f / float64pow10[-exp], i
|
||||
}
|
||||
f *= math.Pow10(int(-mantExp))
|
||||
|
@ -135,7 +135,7 @@ func AppendFloat(b []byte, f float64, prec int) ([]byte, bool) {
|
|||
// expLen is zero for positive exponents, because positive exponents are determined later on in the big conversion loop
|
||||
exp := 0
|
||||
expLen := 0
|
||||
if mantExp > 0 {
|
||||
if 0 < mantExp {
|
||||
// positive exponent is determined in the loop below
|
||||
// but if we initially decreased the exponent to fit in an integer, we can't set the new exponent in the loop alone,
|
||||
// since the number of zeros at the end determines the positive exponent in the loop, and we just artificially lost zeros
|
||||
|
@ -156,7 +156,7 @@ func AppendFloat(b []byte, f float64, prec int) ([]byte, bool) {
|
|||
if neg {
|
||||
maxLen++
|
||||
}
|
||||
if i+maxLen > cap(b) {
|
||||
if cap(b) < i+maxLen {
|
||||
b = append(b, make([]byte, maxLen)...)
|
||||
} else {
|
||||
b = b[:i+maxLen]
|
||||
|
@ -175,17 +175,17 @@ func AppendFloat(b []byte, f float64, prec int) ([]byte, bool) {
|
|||
last := i + mantLen // right-most position of digit that is non-zero + dot
|
||||
dot := last - prec - exp // position of dot
|
||||
j := last
|
||||
for mant > 0 {
|
||||
for 0 < mant {
|
||||
if j == dot {
|
||||
b[j] = '.'
|
||||
j--
|
||||
}
|
||||
newMant := mant / 10
|
||||
digit := mant - 10*newMant
|
||||
if zero && digit > 0 {
|
||||
if zero && 0 < digit {
|
||||
// first non-zero digit, if we are still behind the dot we can trim the end to this position
|
||||
// otherwise trim to the dot (including the dot)
|
||||
if j > dot {
|
||||
if dot < j {
|
||||
i = j + 1
|
||||
// decrease negative exponent further to get rid of dot
|
||||
if exp < 0 {
|
||||
|
@ -209,9 +209,9 @@ func AppendFloat(b []byte, f float64, prec int) ([]byte, bool) {
|
|||
mant = newMant
|
||||
}
|
||||
|
||||
if j > dot {
|
||||
if dot < j {
|
||||
// extra zeros behind the dot
|
||||
for j > dot {
|
||||
for dot < j {
|
||||
b[j] = '0'
|
||||
j--
|
||||
}
|
||||
|
@ -244,7 +244,7 @@ func AppendFloat(b []byte, f float64, prec int) ([]byte, bool) {
|
|||
}
|
||||
i += LenInt(int64(exp))
|
||||
j := i
|
||||
for exp > 0 {
|
||||
for 0 < exp {
|
||||
newExp := exp / 10
|
||||
digit := exp - 10*newExp
|
||||
j--
|
||||
|
|
8
vendor/github.com/tdewolff/parse/v2/strconv/price.go
generated
vendored
8
vendor/github.com/tdewolff/parse/v2/strconv/price.go
generated
vendored
|
@ -20,14 +20,14 @@ func AppendPrice(b []byte, price int64, dec bool, milSeparator byte, decSeparato
|
|||
// rounding
|
||||
if !dec {
|
||||
firstDec := (price / 10) % 10
|
||||
if firstDec >= 5 {
|
||||
if 5 <= firstDec {
|
||||
price += 100
|
||||
}
|
||||
}
|
||||
|
||||
// calculate size
|
||||
n := LenInt(price) - 2
|
||||
if n > 0 {
|
||||
if 0 < n {
|
||||
n += (n - 1) / 3 // mil separator
|
||||
} else {
|
||||
n = 1
|
||||
|
@ -38,7 +38,7 @@ func AppendPrice(b []byte, price int64, dec bool, milSeparator byte, decSeparato
|
|||
|
||||
// resize byte slice
|
||||
i := len(b)
|
||||
if i+n > cap(b) {
|
||||
if cap(b) < i+n {
|
||||
b = append(b, make([]byte, n)...)
|
||||
} else {
|
||||
b = b[:i+n]
|
||||
|
@ -66,7 +66,7 @@ func AppendPrice(b []byte, price int64, dec bool, milSeparator byte, decSeparato
|
|||
|
||||
// print integer-part
|
||||
j := 0
|
||||
for price > 0 {
|
||||
for 0 < price {
|
||||
if j == 3 {
|
||||
b[i] = milSeparator
|
||||
i--
|
||||
|
|
28
vendor/golang.org/x/net/html/render.go
generated
vendored
28
vendor/golang.org/x/net/html/render.go
generated
vendored
|
@ -194,9 +194,8 @@ func render1(w writer, n *Node) error {
|
|||
}
|
||||
}
|
||||
|
||||
// Render any child nodes.
|
||||
switch n.Data {
|
||||
case "iframe", "noembed", "noframes", "noscript", "plaintext", "script", "style", "xmp":
|
||||
// Render any child nodes
|
||||
if childTextNodesAreLiteral(n) {
|
||||
for c := n.FirstChild; c != nil; c = c.NextSibling {
|
||||
if c.Type == TextNode {
|
||||
if _, err := w.WriteString(c.Data); err != nil {
|
||||
|
@ -213,7 +212,7 @@ func render1(w writer, n *Node) error {
|
|||
// last element in the file, with no closing tag.
|
||||
return plaintextAbort
|
||||
}
|
||||
default:
|
||||
} else {
|
||||
for c := n.FirstChild; c != nil; c = c.NextSibling {
|
||||
if err := render1(w, c); err != nil {
|
||||
return err
|
||||
|
@ -231,6 +230,27 @@ func render1(w writer, n *Node) error {
|
|||
return w.WriteByte('>')
|
||||
}
|
||||
|
||||
func childTextNodesAreLiteral(n *Node) bool {
|
||||
// Per WHATWG HTML 13.3, if the parent of the current node is a style,
|
||||
// script, xmp, iframe, noembed, noframes, or plaintext element, and the
|
||||
// current node is a text node, append the value of the node's data
|
||||
// literally. The specification is not explicit about it, but we only
|
||||
// enforce this if we are in the HTML namespace (i.e. when the namespace is
|
||||
// "").
|
||||
// NOTE: we also always include noscript elements, although the
|
||||
// specification states that they should only be rendered as such if
|
||||
// scripting is enabled for the node (which is not something we track).
|
||||
if n.Namespace != "" {
|
||||
return false
|
||||
}
|
||||
switch n.Data {
|
||||
case "iframe", "noembed", "noframes", "noscript", "plaintext", "script", "style", "xmp":
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// writeQuoted writes s to w surrounded by quotes. Normally it will use double
|
||||
// quotes, but if s contains a double quote, it will use single quotes.
|
||||
// It is used for writing the identifiers in a doctype declaration.
|
||||
|
|
35
vendor/golang.org/x/net/http2/transport.go
generated
vendored
35
vendor/golang.org/x/net/http2/transport.go
generated
vendored
|
@ -19,6 +19,7 @@ import (
|
|||
"io/fs"
|
||||
"log"
|
||||
"math"
|
||||
"math/bits"
|
||||
mathrand "math/rand"
|
||||
"net"
|
||||
"net/http"
|
||||
|
@ -518,11 +519,14 @@ func (t *Transport) RoundTrip(req *http.Request) (*http.Response, error) {
|
|||
func authorityAddr(scheme string, authority string) (addr string) {
|
||||
host, port, err := net.SplitHostPort(authority)
|
||||
if err != nil { // authority didn't have a port
|
||||
host = authority
|
||||
port = ""
|
||||
}
|
||||
if port == "" { // authority's port was empty
|
||||
port = "443"
|
||||
if scheme == "http" {
|
||||
port = "80"
|
||||
}
|
||||
host = authority
|
||||
}
|
||||
if a, err := idna.ToASCII(host); err == nil {
|
||||
host = a
|
||||
|
@ -1677,7 +1681,27 @@ func (cs *clientStream) frameScratchBufferLen(maxFrameSize int) int {
|
|||
return int(n) // doesn't truncate; max is 512K
|
||||
}
|
||||
|
||||
var bufPool sync.Pool // of *[]byte
|
||||
// Seven bufPools manage different frame sizes. This helps to avoid scenarios where long-running
|
||||
// streaming requests using small frame sizes occupy large buffers initially allocated for prior
|
||||
// requests needing big buffers. The size ranges are as follows:
|
||||
// {0 KB, 16 KB], {16 KB, 32 KB], {32 KB, 64 KB], {64 KB, 128 KB], {128 KB, 256 KB],
|
||||
// {256 KB, 512 KB], {512 KB, infinity}
|
||||
// In practice, the maximum scratch buffer size should not exceed 512 KB due to
|
||||
// frameScratchBufferLen(maxFrameSize), thus the "infinity pool" should never be used.
|
||||
// It exists mainly as a safety measure, for potential future increases in max buffer size.
|
||||
var bufPools [7]sync.Pool // of *[]byte
|
||||
func bufPoolIndex(size int) int {
|
||||
if size <= 16384 {
|
||||
return 0
|
||||
}
|
||||
size -= 1
|
||||
bits := bits.Len(uint(size))
|
||||
index := bits - 14
|
||||
if index >= len(bufPools) {
|
||||
return len(bufPools) - 1
|
||||
}
|
||||
return index
|
||||
}
|
||||
|
||||
func (cs *clientStream) writeRequestBody(req *http.Request) (err error) {
|
||||
cc := cs.cc
|
||||
|
@ -1695,12 +1719,13 @@ func (cs *clientStream) writeRequestBody(req *http.Request) (err error) {
|
|||
// Scratch buffer for reading into & writing from.
|
||||
scratchLen := cs.frameScratchBufferLen(maxFrameSize)
|
||||
var buf []byte
|
||||
if bp, ok := bufPool.Get().(*[]byte); ok && len(*bp) >= scratchLen {
|
||||
defer bufPool.Put(bp)
|
||||
index := bufPoolIndex(scratchLen)
|
||||
if bp, ok := bufPools[index].Get().(*[]byte); ok && len(*bp) >= scratchLen {
|
||||
defer bufPools[index].Put(bp)
|
||||
buf = *bp
|
||||
} else {
|
||||
buf = make([]byte, scratchLen)
|
||||
defer bufPool.Put(&buf)
|
||||
defer bufPools[index].Put(&buf)
|
||||
}
|
||||
|
||||
var sawEOF bool
|
||||
|
|
1
vendor/golang.org/x/oauth2/internal/client_appengine.go
generated
vendored
1
vendor/golang.org/x/oauth2/internal/client_appengine.go
generated
vendored
|
@ -3,7 +3,6 @@
|
|||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build appengine
|
||||
// +build appengine
|
||||
|
||||
package internal
|
||||
|
||||
|
|
12
vendor/modules.txt
vendored
12
vendor/modules.txt
vendored
|
@ -659,11 +659,11 @@ github.com/superseriousbusiness/oauth2/v4/generates
|
|||
github.com/superseriousbusiness/oauth2/v4/manage
|
||||
github.com/superseriousbusiness/oauth2/v4/models
|
||||
github.com/superseriousbusiness/oauth2/v4/server
|
||||
# github.com/tdewolff/minify/v2 v2.12.7
|
||||
## explicit; go 1.13
|
||||
# github.com/tdewolff/minify/v2 v2.12.8
|
||||
## explicit; go 1.18
|
||||
github.com/tdewolff/minify/v2
|
||||
github.com/tdewolff/minify/v2/html
|
||||
# github.com/tdewolff/parse/v2 v2.6.6
|
||||
# github.com/tdewolff/parse/v2 v2.6.7
|
||||
## explicit; go 1.13
|
||||
github.com/tdewolff/parse/v2
|
||||
github.com/tdewolff/parse/v2/buffer
|
||||
|
@ -852,7 +852,7 @@ golang.org/x/image/webp
|
|||
# golang.org/x/mod v0.10.0
|
||||
## explicit; go 1.17
|
||||
golang.org/x/mod/semver
|
||||
# golang.org/x/net v0.12.0
|
||||
# golang.org/x/net v0.14.0
|
||||
## explicit; go 1.17
|
||||
golang.org/x/net/bpf
|
||||
golang.org/x/net/context
|
||||
|
@ -870,8 +870,8 @@ golang.org/x/net/ipv4
|
|||
golang.org/x/net/ipv6
|
||||
golang.org/x/net/publicsuffix
|
||||
golang.org/x/net/trace
|
||||
# golang.org/x/oauth2 v0.10.0
|
||||
## explicit; go 1.17
|
||||
# golang.org/x/oauth2 v0.11.0
|
||||
## explicit; go 1.18
|
||||
golang.org/x/oauth2
|
||||
golang.org/x/oauth2/internal
|
||||
# golang.org/x/sys v0.11.0
|
||||
|
|
45
web/assets/feditext.svg
Normal file
45
web/assets/feditext.svg
Normal file
|
@ -0,0 +1,45 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
version="1.1"
|
||||
id="svg1"
|
||||
width="580"
|
||||
height="648.07129"
|
||||
viewBox="0 0 580 648.07129"
|
||||
sodipodi:docname="feditext.svg"
|
||||
inkscape:version="1.3 (0e150ed6c4, 2023-07-21)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs1" />
|
||||
<sodipodi:namedview
|
||||
id="namedview1"
|
||||
pagecolor="#505050"
|
||||
bordercolor="#ffffff"
|
||||
borderopacity="1"
|
||||
inkscape:showpageshadow="0"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pagecheckerboard="1"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
inkscape:zoom="0.60078125"
|
||||
inkscape:cx="643.329"
|
||||
inkscape:cy="640"
|
||||
inkscape:window-width="2752"
|
||||
inkscape:window-height="1083"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="layer1" />
|
||||
<g
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
inkscape:label="Image 1">
|
||||
<path
|
||||
style="fill:#ffffff;stroke-width:1.06266"
|
||||
d="m 178.31448,648.0457 c 0,-0.22287 -3.31804,-12.73571 -7.37341,-27.8064 C 166.88568,605.16868 147.31446,532.1071 127.44944,457.88029 85.527985,301.23825 87.197265,307.36035 86.112035,306.27513 c -0.46091,-0.46092 -14.21456,2.67442 -30.56365,6.96742 -16.349101,4.293 -31.208011,8.12989 -33.019811,8.52642 l -3.29419,0.72096 -6.51865,-24.22492 C 2.0285837,258.5489 -0.57989635,248.01817 0.10089365,247.33738 c 0.35549,-0.3555 14.89589035,-4.46095 32.31199035,-9.12325 17.416101,-4.66229 32.068961,-8.85856 32.561911,-9.32506 0.49294,-0.46651 -1.98796,-11.77814 -5.51312,-25.13696 -8.38344,-31.76958 -9.39714,-36.33345 -11.30815,-50.91138 -5.98718,-45.67279 5.20679,-81.973838 33.81088,-109.645688 21.331845,-20.63662 48.691955,-33.6522399 86.724635,-41.2562299 10.997,-2.19866004 13.45942,-2.40581004 14.63412,-1.23110004 1.13937,1.13937004 22.62048,78.49091794 22.62048,81.45430794 0,0.46185 -4.05603,1.64524 -9.0134,2.62977 -24.76037,4.91735 -40.46005,14.46453 -46.82825,28.47687 -3.90046,8.5824 -4.77238,23.27239 -2.19535,36.98665 1.75696,9.35007 12.75641,51.76056 13.65838,52.66253 0.4428,0.44282 121.76948,-31.65747 131.29224,-34.73694 2.50328,-0.8095 3.34893,-1.63052 2.9807,-2.8938 -4.05367,-13.9066 -28.24159,-106.808348 -27.89809,-107.151858 0.45121,-0.4512 89.39306,-24.41748 92.86632,-25.02376 1.37732,-0.24042 4.22212,9.07873 15.31855,50.181108 7.4949,27.76199 14.02122,51.91095 14.50292,53.66433 0.53811,1.9588 1.67104,3.29946 2.93832,3.4771 1.13437,0.15901 18.23012,-4.02521 37.99054,-9.29827 19.76042,-5.27307 36.58465,-9.5874 37.38716,-9.5874 0.80252,0 1.70596,0.83685 2.00766,1.85966 1.15593,3.91878 15.49189,57.51001 17.43187,65.16443 l 2.03934,8.04645 -38.34515,10.31829 c -21.08983,5.67507 -38.67242,10.66171 -39.07242,11.08144 -0.56974,0.59783 48.98078,187.79383 57.02149,215.42048 4.67507,16.06282 13.21057,28.01206 23.68276,33.15461 5.63954,2.76939 6.94454,2.97246 18.81187,2.92708 11.85639,-0.0453 13.49817,-0.31471 23.37852,-3.83585 5.8446,-2.0829 13.37624,-5.04366 16.73685,-6.57947 3.36068,-1.53581 6.11032,-2.55248 6.11032,-2.25928 0,0.29321 4.782,18.33159 10.6266,40.08529 C 575.21806,518.68519 580,537.03684 580,537.71293 c 0,1.63704 -22.9762,11.00659 -38.25578,15.60048 -25.7852,7.75246 -32.1521,8.71291 -57.38362,8.65622 -22.14919,-0.0498 -23.12316,-0.14288 -31.8798,-3.04791 -29.12764,-9.66317 -49.29263,-28.46977 -63.86201,-59.56002 -6.69221,-14.28079 -9.86436,-25.16989 -39.68012,-136.21013 -35.98923,-134.03153 -31.51618,-118.94286 -35.13039,-118.50324 -3.44623,0.41919 -124.18412,32.49994 -128.5205,34.14862 -2.5808,0.98123 -2.81004,1.46652 -2.14776,4.54676 0.75671,3.51942 6.27829,24.2355 54.87563,205.88505 34.86397,130.31626 35.4586,132.60367 34.67505,133.38728 -0.55047,0.55045 -26.01282,7.54339 -82.42129,22.6361 -6.57521,1.75925 -11.95493,3.01638 -11.95493,2.79356 z"
|
||||
id="path2-3" />
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 4 KiB |
|
@ -55,6 +55,14 @@
|
|||
<a href="https://tusky.app" target="_blank" rel="noopener">Get Tusky</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="entry">
|
||||
<img class="logo" src="/assets/feditext.svg" alt="The Feditext logo, the characters ft at a slight angle">
|
||||
<div>
|
||||
<h2>Feditext</h2>
|
||||
<p>Feditext (beta) is a beautiful client for iOS, iPadOS and macOS.</p>
|
||||
<a href="https://fedi.software/@Feditext" target="_blank" rel="noopener">Get Feditext</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="entry">
|
||||
<img class="logo" src="/assets/mastodon.svg" alt="The Mastodon logo, the character M in a speech bubble">
|
||||
<div>
|
||||
|
|
Loading…
Reference in a new issue