30 Commits
Author SHA1 Message Date
Matt BaerandGitHub aacd7d3d0c Merge pull request #4 from laosb/patch-1
Improve Chinese translation.
2020-09-24 10:58:41 -04:00
Matt BaerandGitHub 3134869cce Merge pull request #8 from eddsalkield/nerdsnipe
Remove dependency on github.com/writeas/nerds
2020-09-24 08:45:19 -04:00
Matt BaerandGitHub 083de67f45 Merge pull request #6 from eddsalkield/uuid
Migrate to new uuid library
2020-09-24 08:25:13 -04:00
Edd Salkield 339af195c2 Remove dependency on github.com/writeas/nerds 2020-09-22 22:14:10 +00:00
Edd Salkield 83e2689111 Migrate to new uuid library 2020-09-22 22:06:36 +00:00
Matt Baer 68a680d1b0 Add silobridge package 2020-08-13 12:17:34 -04:00
Matt Baer f54ee176fc Remove commentsEnabled AP extension
This isn't used.
2019-11-11 21:56:08 +09:00
Matt Baer 45b1985b97 Change Write.as -> WriteFreely in README 2019-11-11 21:48:19 +09:00
Wexpo LyuandGitHub 35ecbe8f09 Update Chinese translation.
- Make sentences concise.
- Implement spaces between Chinese and Western characters.
2019-10-26 17:42:19 +08:00
Matt Baer 46304124ec Document posts.ExtractTitle func 2019-05-30 10:12:34 -04:00
Matt Baer 1f29da565f Include calling filename and line # in error logs
...instead of the filename and line number of the Printf call in
log.Error()
2019-05-23 06:59:22 -04:00
Matt BaerandGitHub 8dbb4ebdc5 Merge pull request #3 from gytisrepecka/master
Added phrases_lt.go
2019-05-08 09:51:00 -04:00
Gytis Repečka c5dba02ee6 Added Lithuanian localization. 2019-05-08 08:18:35 +03:00
Matt Baer 7b4bf5c1b2 Include extensions when building Create & Update activities 2019-04-04 16:57:46 -04:00
Matt Baer 78107a0269 Merge branch 'master' of github.com:writeas/web-core 2019-04-04 16:36:11 -04:00
Matt Baer 528e59e922 Update README to explain use in WriteFreely 2019-04-04 16:35:50 -04:00
Matt Baer ad1a4272b0 Support new commentsEnabled property
This adds a CommentsEnabled field to the activitystreams.Object struct,
as well as the Extensions needed in the Context field in order to
describe the field.

This is a field previously supported by PeerTube, and just recently
added in PixelFed.
2019-04-04 16:29:00 -04:00
Matt BaerandGitHub 9884cdaa8a Merge pull request #2 from SamWhited/support_modules
Support Go Modules
2019-02-01 17:30:18 +01:00
Sam Whited cee61547fa Support Go Modules
Fixes #1
2019-02-01 10:28:46 -06:00
Matt Baer ce8cb5ee19 Use #writefreely badge, not Slack 2019-01-10 21:42:25 -05:00
Matt Baer 5631982c2b Support creating word-ish password
This adds the NewWordish() func
2019-01-10 20:46:03 -05:00
Matt Baer 4cb17a6518 Add title dash
This defaults to the em dash, but uses an en dash on German posts.

Part of writeas/writefreely#1
2018-12-24 13:48:21 -05:00
Matt Baer 05f387ffa1 Omit empty endpoints.sharedInbox
This fixes writeas/writefreely#8
2018-11-11 11:55:28 -05:00
Matt Baer 265880c2cf Add tags package documentation 2018-10-28 12:01:02 -04:00
Matt Baer 316410bd35 Don't fail test when repeat slug generated 2018-10-16 23:21:12 -04:00
Matt Baer 3d47537e1f Add stringmanip package 2018-10-16 22:58:06 -04:00
Matt Baer 567e0c6ef0 Add i18n.LangIsRTL func 2018-10-16 20:38:27 -04:00
Matt Baer 73bffef9af Add tags.Extract() func 2018-10-16 20:33:34 -04:00
Matt Baer e5a5d63c6f Add Convert(JSON|SQL) funcs 2018-10-16 18:26:08 -04:00
Matt Baer 6c98a50085 Add safe slug generation func in package id 2018-10-15 17:41:41 -04:00
24 changed files with 479 additions and 25 deletions
+4 -4
View File
@@ -1,8 +1,8 @@
Write.as web core
=================
WriteFreely web core
====================
[![GoDoc](https://godoc.org/github.com/writeas/web-core?status.svg)](https://godoc.org/github.com/writeas/web-core)
[![Build Status](https://travis-ci.org/writeas/web-core.svg)](https://travis-ci.org/writeas/web-core)
[![Public Slack discussion](http://slack.write.as/badge.svg)](http://slack.write.as/)
[![#writefreely on freenode](https://img.shields.io/badge/freenode-%23writefreely-blue.svg)](http://webchat.freenode.net/?channels=writefreely)
[![Discuss on our forum](https://img.shields.io/discourse/https/discuss.write.as/users.svg?label=forum)](https://discuss.write.as/c/development)
web-core holds components of the [Write.as](https://write.as) web application.
web-core holds components of the [WriteFreely](https://writefreely.org) web application, and is shared with other [Write.as](https://write.as) products, like [Snap.as](https://snap.as).
+7
View File
@@ -11,6 +11,8 @@ const (
toPublic = "https://www.w3.org/ns/activitystreams#Public"
)
var Extensions = map[string]string{}
// Activity describes actions that have either already occurred, are in the
// process of occurring, or may occur in the future.
type Activity struct {
@@ -38,6 +40,7 @@ func NewCreateActivity(o *Object) *Activity {
BaseObject: BaseObject{
Context: []interface{}{
Namespace,
Extensions,
},
ID: o.ID,
Type: "Create",
@@ -56,6 +59,7 @@ func NewUpdateActivity(o *Object) *Activity {
BaseObject: BaseObject{
Context: []interface{}{
Namespace,
Extensions,
},
ID: o.ID,
Type: "Update",
@@ -113,6 +117,9 @@ type Object struct {
Content string `json:"content"`
ContentMap map[string]string `json:"contentMap,omitempty"`
Tag []Tag `json:"tag"`
// Extensions
// NOTE: add extensions here
}
// NewNoteObject creates a basic Note object that includes the public
+1 -1
View File
@@ -17,7 +17,7 @@ type (
}
Endpoints struct {
SharedInbox string `json:"sharedInbox"`
SharedInbox string `json:"sharedInbox,omitempty"`
}
Image struct {
+3 -3
View File
@@ -1,7 +1,7 @@
package auth
import (
uuid "github.com/nu7hatch/gouuid"
uuid "github.com/gofrs/uuid"
"github.com/writeas/web-core/log"
"strings"
)
@@ -16,7 +16,7 @@ func GetToken(header string) []byte {
token = f[1]
}
}
t, err := uuid.ParseHex(token)
t, err := uuid.FromString(token)
if err != nil {
log.Error("Couldn't parseHex on '%s': %v", accessToken, err)
} else {
@@ -31,7 +31,7 @@ func GetHeaderToken(header string) []byte {
if len(header) > 0 {
f := strings.Fields(header)
if len(f) == 2 && f[0] == "Token" {
t, err := uuid.ParseHex(f[1])
t, err := uuid.FromString(f[1])
if err != nil {
log.Error("Couldn't parseHex on '%s': %v", accessToken, err)
} else {
+21
View File
@@ -80,3 +80,24 @@ func (v *NullJSONString) UnmarshalJSON(data []byte) error {
}
return nil
}
func ConvertJSONNullString(value string) reflect.Value {
v := NullJSONString{}
if err := v.Scan(value); err != nil {
return reflect.Value{}
}
return reflect.ValueOf(v)
}
func ConvertJSONNullBool(value string) reflect.Value {
v := NullJSONBool{}
if value == "on" || value == "off" {
return reflect.ValueOf(NullJSONBool{sql.NullBool{Bool: value == "on", Valid: true}})
}
if err := v.Scan(value); err != nil {
return reflect.Value{}
}
return reflect.ValueOf(v)
}
+36
View File
@@ -40,3 +40,39 @@ func SQLNullFloat64(value string) reflect.Value {
return reflect.ValueOf(v)
}
func ConvertSQLNullString(value string) reflect.Value {
v := sql.NullString{}
if err := v.Scan(value); err != nil {
return reflect.Value{}
}
return reflect.ValueOf(v)
}
func ConvertSQLNullBool(value string) reflect.Value {
v := sql.NullBool{}
if err := v.Scan(value); err != nil {
return reflect.Value{}
}
return reflect.ValueOf(v)
}
func ConvertSQLNullInt64(value string) reflect.Value {
v := sql.NullInt64{}
if err := v.Scan(value); err != nil {
return reflect.Value{}
}
return reflect.ValueOf(v)
}
func ConvertSQLNullFloat64(value string) reflect.Value {
v := sql.NullFloat64{}
if err := v.Scan(value); err != nil {
return reflect.Value{}
}
return reflect.ValueOf(v)
}
+17
View File
@@ -0,0 +1,17 @@
module github.com/writeas/web-core
go 1.10
require (
github.com/gofrs/uuid v3.3.0+incompatible
github.com/kr/pretty v0.1.0 // indirect
github.com/kylemcc/twitter-text-go v0.0.0-20180726194232-7f582f6736ec
github.com/microcosm-cc/bluemonday v1.0.2
github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect
github.com/writeas/impart v1.1.0
github.com/writeas/openssl-go v1.0.0
github.com/writeas/saturday v1.6.0
golang.org/x/crypto v0.0.0-20190131182504-b8fe1690c613
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect
gopkg.in/yaml.v1 v1.0.0-20140924161607-9f9df34309c0 // indirect
)
+28
View File
@@ -0,0 +1,28 @@
github.com/gofrs/uuid v1.2.0 h1:coDhrjgyJaglxSjxuJdqQSSdUpG3w6p1OwN2od6frBU=
github.com/gofrs/uuid v3.3.0+incompatible h1:8K4tyRfvU1CYPgJsveYFQMhpFd/wXNM7iK6rR7UHz84=
github.com/gofrs/uuid v3.3.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kylemcc/twitter-text-go v0.0.0-20180726194232-7f582f6736ec h1:ZXWuspqypleMuJy4bzYEqlMhJnGAYpLrWe5p7W3CdvI=
github.com/kylemcc/twitter-text-go v0.0.0-20180726194232-7f582f6736ec/go.mod h1:voECJzdraJmolzPBgL9Z7ANwXf4oMXaTCsIkdiPpR/g=
github.com/microcosm-cc/bluemonday v1.0.2 h1:5lPfLTTAvAbtS0VqT+94yOtFnGfUWYyx0+iToC3Os3s=
github.com/microcosm-cc/bluemonday v1.0.2/go.mod h1:iVP4YcDBq+n/5fb23BhYFvIMq/leAFZyRl6bYmGDlGc=
github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/writeas/impart v1.1.0 h1:nPnoO211VscNkp/gnzir5UwCDEvdHThL5uELU60NFSE=
github.com/writeas/impart v1.1.0/go.mod h1:g0MpxdnTOHHrl+Ca/2oMXUHJ0PcRAEWtkCzYCJUXC9Y=
github.com/writeas/openssl-go v1.0.0 h1:YXM1tDXeYOlTyJjoMlYLQH1xOloUimSR1WMF8kjFc5o=
github.com/writeas/openssl-go v1.0.0/go.mod h1:WsKeK5jYl0B5y8ggOmtVjbmb+3rEGqSD25TppjJnETA=
github.com/writeas/saturday v1.6.0 h1:HNUtX8TVJJnSdxE8vaAgtHiAJVt+Of5AJYlm62pmVlI=
github.com/writeas/saturday v1.6.0/go.mod h1:ETE1EK6ogxptJpAgUbcJD0prAtX48bSloie80+tvnzQ=
golang.org/x/crypto v0.0.0-20190131182504-b8fe1690c613 h1:MQ/ZZiDsUapFFiMS+vzwXkCTeEKaum+Do5rINYJDmxc=
golang.org/x/crypto v0.0.0-20190131182504-b8fe1690c613/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/net v0.0.0-20181220203305-927f97764cc3 h1:eH6Eip3UpmR+yM/qI9Ijluzb1bNv/cAU/n+6l8tRSis=
golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v1 v1.0.0-20140924161607-9f9df34309c0 h1:POO/ycCATvegFmVuPpQzZFJ+pGZeX22Ufu6fibxDVjU=
gopkg.in/yaml.v1 v1.0.0-20140924161607-9f9df34309c0/go.mod h1:WDnlLJ4WF5VGsH/HVa3CI79GS0ol3YnhVnKP89i0kNg=
+21
View File
@@ -0,0 +1,21 @@
package i18n
var rtlLangs = map[string]bool{
"ar": true, // Arabic
"dv": true, // Divehi
"fa": true, // Persian (Farsi)
"ha": true, // Hausa
"he": true, // Hebrew
"iw": true, // Hebrew (old code)
"ji": true, // Yiddish (old code)
"ps": true, // Pashto, Pushto
"ur": true, // Urdu
"yi": true, // Yiddish
}
func LangIsRTL(lang string) bool {
if _, ok := rtlLangs[lang]; ok {
return true
}
return false
}
+26
View File
@@ -0,0 +1,26 @@
package id
import (
"fmt"
"crypto/rand"
)
// GenerateRandomString creates a random string of characters of the given
// length from the given dictionary of possible characters.
//
// This example generates a hexadecimal string 6 characters long:
// GenerateRandomString("0123456789abcdef", 6)
func GenerateRandomString(dictionary string, l int) string {
var bytes = make([]byte, l)
rand.Read(bytes)
for k, v := range bytes {
bytes[k] = dictionary[v%byte(len(dictionary))]
}
return string(bytes)
}
// GenSafeUniqueSlug generatees a reasonably unique random slug from the given
// original slug. It's "safe" because it uses 0-9 b-z excluding vowels.
func GenSafeUniqueSlug(slug string) string {
return fmt.Sprintf("%s-%s", slug, GenerateRandomString("0123456789bcdfghjklmnpqrstvwxyz", 4))
}
+19
View File
@@ -0,0 +1,19 @@
package id
import "testing"
func TestGenSafeUniqueSlug(t *testing.T) {
slug := "slug"
r := map[string]bool{}
for i := 0; i < 1000; i++ {
s := GenSafeUniqueSlug(slug)
if s == slug {
t.Errorf("Got same slug as inputted!")
}
if _, ok := r[s]; ok {
t.Logf("#%d: slug %s was already generated in testing.", i, s)
}
r[s] = true
}
}
+1
View File
@@ -32,4 +32,5 @@ var phrases = map[string]string{
"share modal title": "Share this post",
"share": "share",
"unpin": "unpin",
"title dash": "&mdash;",
}
+1
View File
@@ -23,4 +23,5 @@ var phrasesDE = map[string]string{
"share modal title": "Teile diesen Beitrag",
"share": "Teilen",
"unpin": "Lösen",
"title dash": "&ndash;",
}
+27
View File
@@ -0,0 +1,27 @@
package l10n
var phrasesLT = map[string]string{
"Anonymous post": "Anoniminis įrašas",
"Blogs": "Tinklaraščiai",
"Enter": "Įvesti",
"Newer": "Naujesni",
"Older": "Senesni",
"Posts": "Įrašai",
"Publish to...": "Publikuoti į...",
"Publish": "Publikuoti",
"Read more...": "Skaityti daugiau...",
"This blog requires a password.": "Šis tinklaraštis reikalauja slaptažodžio.",
"Toggle theme": "Keisti temą",
"View posts": "Peržiūrėti įrašus",
"delete": "ištrinti",
"edit": "koreguoti",
"move to...": "perkelti į...",
"pin": "prisegti",
"published with write.as": "publikuojama su write.as",
"share modal ending": "Siųskite draugui, dalinkitės internete ar per Twitter. Sužinokite daugiau.",
"share modal introduction": "Kiekvienas publikuotas įrašas turi slaptą, unikalų URL kuriuo galite pasidalinti su bet kuo. Šis URL yra:",
"share modal title": "Dalintis šiuo įrašu",
"share": "dalintis",
"unpin": "atsegti",
"title dash": "&ndash;",
}
+8 -8
View File
@@ -3,24 +3,24 @@ package l10n
var phrasesZH = map[string]string{
"Anonymous post": "匿名文章",
"Blogs": "博客",
"Enter": "按下Enter按钮",
"Newer": "最近的博客",
"Older": "之前的博客",
"Enter": "按下回车",
"Newer": "更新",
"Older": "较旧",
"Posts": "文章",
"Publish to...": "发布到...",
"Publish": "发布",
"Read more...": "阅读更多",
"This blog requires a password.": "这篇博客需要密码",
"This blog requires a password.": "这篇文章需要密码",
"Toggle theme": "更换主题",
"View posts": "查看文章",
"delete": "删除",
"edit": "编辑",
"move to...": "移动到",
"pin": "固定文章",
"published with write.as": "write.as发布",
"share modal ending": "发送给朋友,或者线上分享,如果可能的话,分享到推特上。了解更多。",
"share modal introduction": "发布的每篇文章都有一个唯一的私有URL,你可以把它分享给任何人。这是URL",
"share modal title": "分享这篇文章",
"published with write.as": "通过 write.as 发布",
"share modal ending": "发送给朋友、与世界分享或者发条推。了解更多。",
"share modal introduction": "发布的每篇文章都有一个唯一的私有网址,你可以把它分享给任何人:",
"share modal title": "分享文章",
"share": "分享",
"unpin": "取消固定",
}
+2
View File
@@ -24,6 +24,8 @@ func Strings(lang string) map[string]string {
return phrasesIT
case "ja":
return phrasesJA
case "lt":
return phrasesLT
case "mk":
return phrasesMK
case "pl":
+17 -2
View File
@@ -2,8 +2,10 @@
package log
import (
"fmt"
"log"
"os"
"runtime"
)
var (
@@ -13,7 +15,7 @@ var (
func init() {
InfoLog = log.New(os.Stdout, "", log.Ldate|log.Ltime)
ErrorLog = log.New(os.Stderr, "ERROR: ", log.Ldate|log.Ltime|log.Lshortfile)
ErrorLog = log.New(os.Stderr, "ERROR: ", log.Ldate|log.Ltime)
}
// Info logs an informational message to Stdout.
@@ -23,5 +25,18 @@ func Info(s string, v ...interface{}) {
// Error logs an error to Stderr.
func Error(s string, v ...interface{}) {
ErrorLog.Printf(s, v...)
// Include original caller information
_, file, line, _ := runtime.Caller(1)
// Determine short filename (from standard log package)
short := file
for i := len(file) - 1; i > 0; i-- {
if file[i] == '/' {
short = file[i+1:]
break
}
}
file = short
ErrorLog.Printf(fmt.Sprintf("%s:%d: ", short, line)+s, v...)
}
+7 -7
View File
@@ -1,4 +1,4 @@
// Package passgen generates random, unmemorable passwords.
// Package passgen generates random passwords.
//
// Example usage:
//
@@ -19,20 +19,20 @@ const DefLen = 20
// DefChars is the default set of characters used in the password.
var DefChars = []byte("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*()- _=+,.?/:;{}[]`~")
// New returns a random password of the default length with the default set of
// characters.
// New returns a random, unmemorable password of the default length with the
// default set of characters.
func New() string {
return NewLenChars(DefLen, DefChars)
}
// NewLen returns a random password of the given length with the default set
// of characters.
// NewLen returns a random, unmemorable password of the given length with the
// default set of characters.
func NewLen(length int) string {
return NewLenChars(length, DefChars)
}
// NewLenChars returns a random password of the given length with the given
// set of characters.
// NewLenChars returns a random, unmemorable password of the given length with
// the given set of characters.
func NewLenChars(length int, chars []byte) string {
if length == 0 {
return ""
+64
View File
@@ -0,0 +1,64 @@
package passgen
import (
"crypto/rand"
"math/big"
)
var (
ar = []rune("aA4")
cr = []rune("cC")
er = []rune("eE3")
fr = []rune("fF")
gr = []rune("gG")
hr = []rune("hH")
ir = []rune("iI1")
lr = []rune("lL")
nr = []rune("nN")
or = []rune("oO0")
rr = []rune("rR")
sr = []rune("sS5")
tr = []rune("tT7")
remr = []rune("bcdfghjklmnpqrstvwxyzBCDFGHJKLMNPQRSTVWXYZ0123456789")
)
// NewWordish generates a password made of word-like words.
func NewWordish() string {
b := []rune{}
b = append(b, randLetter(cr))
b = append(b, randLetter(hr))
b = append(b, randLetter(ar))
b = append(b, randLetter(nr))
b = append(b, randLetter(gr))
b = append(b, randLetter(er))
b = append(b, randLetter(tr))
b = append(b, randLetter(hr))
b = append(b, randLetter(ir))
b = append(b, randLetter(sr))
b = append(b, randLetter(ar))
b = append(b, randLetter(fr))
b = append(b, randLetter(tr))
b = append(b, randLetter(er))
b = append(b, randLetter(rr))
b = append(b, randLetter(lr))
b = append(b, randLetter(or))
b = append(b, randLetter(gr))
b = append(b, randLetter(gr))
b = append(b, randLetter(ir))
b = append(b, randLetter(nr))
b = append(b, randLetter(gr))
b = append(b, randLetter(ir))
b = append(b, randLetter(nr))
for i := 0; i <= 7; i++ {
b = append(b, randLetter(remr))
}
return string(b)
}
func randLetter(l []rune) rune {
li, err := rand.Int(rand.Reader, big.NewInt(int64(len(l))))
if err != nil {
return rune(-1)
}
return l[li.Int64()]
}
+2
View File
@@ -4,6 +4,8 @@ import (
"strings"
)
// ExtractTitle takes the given raw post text and returns a title, if explicitly
// provided, and a body.
func ExtractTitle(content string) (title string, body string) {
if hashIndex := strings.Index(content, "# "); hashIndex == 0 {
eol := strings.IndexRune(content, '\n')
+29
View File
@@ -0,0 +1,29 @@
package silobridge
// fakeAPInstances contains a list of sites that we allow writers to mention
// with the @handle@instance.tld syntax, plus the corresponding prefix to
// insert between `https://instance.tld/` and `handle` (e.g.
// https://medium.com/@handle)
var fakeAPInstances = map[string]string{
"deviantart.com": "",
"facebook.com": "",
"flickr.com": "photos/",
"github.com": "",
"instagram.com": "",
"medium.com": "@",
"reddit.com": "user/",
"twitter.com": "",
"wattpad.com": "user/",
"youtube.com": "user/",
}
// Profile returns the full profile URL for a fake ActivityPub instance, based
// on the given handle and domain. If the domain isn't recognized, an empty
// string is returned.
func Profile(handle, domain string) string {
prefix, ok := fakeAPInstances[domain]
if !ok {
return ""
}
return "https://" + domain + "/" + prefix + handle
}
+96
View File
@@ -0,0 +1,96 @@
package stringmanip
/*
The MIT License (MIT)
Copyright (c) 2016 Sergey Kamardin
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
// Source: https://github.com/gobwas/glob/blob/master/util/runes/runes.go
func IndexRune(str string, r rune) int {
s := []rune(str)
for i, c := range s {
if c == r {
return i
}
}
return -1
}
func LastIndexRune(str string, needle rune) int {
s := []rune(str)
needles := []rune{needle}
ls, ln := len(s), len(needles)
switch {
case ln == 0:
if ls == 0 {
return 0
}
return ls
case ln == 1:
return IndexLastRune(s, needles[0])
case ln == ls:
if EqualRunes(s, needles) {
return 0
}
return -1
case ln > ls:
return -1
}
head:
for i := ls - 1; i >= 0 && i >= ln; i-- {
for y := ln - 1; y >= 0; y-- {
if s[i-(ln-y-1)] != needles[y] {
continue head
}
}
return i - ln + 1
}
return -1
}
func IndexLastRune(s []rune, r rune) int {
for i := len(s) - 1; i >= 0; i-- {
if s[i] == r {
return i
}
}
return -1
}
func EqualRunes(a, b []rune) bool {
if len(a) == len(b) {
for i := 0; i < len(a); i++ {
if a[i] != b[i] {
return false
}
}
return true
}
return false
}
+17
View File
@@ -0,0 +1,17 @@
package stringmanip
// Substring provides a safe way to extract a substring from a UTF-8 string.
// From this discussion:
// https://groups.google.com/d/msg/golang-nuts/cGq1Irv_5Vs/0SKoj49BsWQJ
func Substring(s string, p, l int) string {
if p < 0 || l <= 0 {
return ""
}
c := []rune(s)
if p > len(c) {
return ""
} else if p+l > len(c) || p+l < p {
return string(c[p:])
}
return string(c[p : p+l])
}
+25
View File
@@ -0,0 +1,25 @@
// Package tags supports operations around hashtags in plain text content
package tags
import (
"github.com/kylemcc/twitter-text-go/extract"
)
// Extract finds all hashtags in the given string and returns a de-duplicated
// list of them.
func Extract(body string) []string {
matches := extract.ExtractHashtags(body)
tags := map[string]bool{}
for i := range matches {
// Second value (whether or not there's a hashtag) ignored here, since
// we're only extracting hashtags.
ht, _ := matches[i].Hashtag()
tags[ht] = true
}
resTags := make([]string, 0)
for k := range tags {
resTags = append(resTags, k)
}
return resTags
}