diff --git a/bridgev2/portal.go b/bridgev2/portal.go index aa7a02ea..43a2e21e 100644 --- a/bridgev2/portal.go +++ b/bridgev2/portal.go @@ -2153,7 +2153,7 @@ func (portal *Portal) ProcessChatInfoChange(ctx context.Context, sender EventSen portal.UpdateInfo(ctx, change.ChatInfo, source, intent, ts) } if change.MemberChanges != nil { - err := portal.SyncParticipants(ctx, change.MemberChanges, source, intent, ts) + err := portal.syncParticipants(ctx, change.MemberChanges, source, intent, ts) if err != nil { zerolog.Ctx(ctx).Err(err).Msg("Failed to sync room members") } @@ -2274,7 +2274,7 @@ type UserLocalPortalInfo struct { Tag *event.RoomTag } -func (portal *Portal) UpdateName(ctx context.Context, name string, sender MatrixAPI, ts time.Time) bool { +func (portal *Portal) updateName(ctx context.Context, name string, sender MatrixAPI, ts time.Time) bool { if portal.Name == name && (portal.NameSet || portal.MXID == "") { return false } @@ -2283,7 +2283,7 @@ func (portal *Portal) UpdateName(ctx context.Context, name string, sender Matrix return true } -func (portal *Portal) UpdateTopic(ctx context.Context, topic string, sender MatrixAPI, ts time.Time) bool { +func (portal *Portal) updateTopic(ctx context.Context, topic string, sender MatrixAPI, ts time.Time) bool { if portal.Topic == topic && (portal.TopicSet || portal.MXID == "") { return false } @@ -2292,7 +2292,7 @@ func (portal *Portal) UpdateTopic(ctx context.Context, topic string, sender Matr return true } -func (portal *Portal) UpdateAvatar(ctx context.Context, avatar *Avatar, sender MatrixAPI, ts time.Time) bool { +func (portal *Portal) updateAvatar(ctx context.Context, avatar *Avatar, sender MatrixAPI, ts time.Time) bool { if portal.AvatarID == avatar.ID && (portal.AvatarSet || portal.MXID == "") { return false } @@ -2407,7 +2407,7 @@ func (portal *Portal) sendRoomMeta(ctx context.Context, sender MatrixAPI, ts tim return true } -func (portal *Portal) GetInitialMemberList(ctx context.Context, members *ChatMemberList, source *UserLogin, pl *event.PowerLevelsEventContent) (invite, functional []id.UserID, err error) { +func (portal *Portal) getInitialMemberList(ctx context.Context, members *ChatMemberList, source *UserLogin, pl *event.PowerLevelsEventContent) (invite, functional []id.UserID, err error) { if members == nil { invite = []id.UserID{source.UserMXID} return @@ -2480,7 +2480,7 @@ func (portal *Portal) updateOtherUser(ctx context.Context, members *ChatMemberLi return false } -func (portal *Portal) SyncParticipants(ctx context.Context, members *ChatMemberList, source *UserLogin, sender MatrixAPI, ts time.Time) error { +func (portal *Portal) syncParticipants(ctx context.Context, members *ChatMemberList, source *UserLogin, sender MatrixAPI, ts time.Time) error { var loginsInPortal []*UserLogin var err error if members.CheckAllLogins { @@ -2721,7 +2721,7 @@ func (portal *Portal) UpdateDisappearingSetting(ctx context.Context, setting dat return true } -func (portal *Portal) UpdateParent(ctx context.Context, newParent networkid.PortalID, source *UserLogin) bool { +func (portal *Portal) updateParent(ctx context.Context, newParent networkid.PortalID, source *UserLogin) bool { if portal.ParentID == newParent { return false } @@ -2773,8 +2773,8 @@ func (portal *Portal) UpdateInfoFromGhost(ctx context.Context, ghost *Ghost) (ch return } } - changed = portal.UpdateName(ctx, ghost.Name, nil, time.Time{}) || changed - changed = portal.UpdateAvatar(ctx, &Avatar{ + changed = portal.updateName(ctx, ghost.Name, nil, time.Time{}) || changed + changed = portal.updateAvatar(ctx, &Avatar{ ID: ghost.AvatarID, MXC: ghost.AvatarMXC, Hash: ghost.AvatarHash, @@ -2787,28 +2787,28 @@ func (portal *Portal) UpdateInfo(ctx context.Context, info *ChatInfo, source *Us changed := false if info.Name != nil { portal.NameIsCustom = true - changed = portal.UpdateName(ctx, *info.Name, sender, ts) || changed + changed = portal.updateName(ctx, *info.Name, sender, ts) || changed } if info.Topic != nil { - changed = portal.UpdateTopic(ctx, *info.Topic, sender, ts) || changed + changed = portal.updateTopic(ctx, *info.Topic, sender, ts) || changed } if info.Avatar != nil { portal.NameIsCustom = true - changed = portal.UpdateAvatar(ctx, info.Avatar, sender, ts) || changed + changed = portal.updateAvatar(ctx, info.Avatar, sender, ts) || changed } changed = portal.UpdateInfoFromGhost(ctx, nil) || changed if info.Disappear != nil { changed = portal.UpdateDisappearingSetting(ctx, *info.Disappear, sender, ts, false, false) || changed } if info.ParentID != nil { - changed = portal.UpdateParent(ctx, *info.ParentID, source) || changed + changed = portal.updateParent(ctx, *info.ParentID, source) || changed } if info.JoinRule != nil { // TODO change detection instead of spamming this every time? portal.sendRoomMeta(ctx, sender, ts, event.StateJoinRules, "", info.JoinRule) } if info.Members != nil && portal.MXID != "" && source != nil { - err := portal.SyncParticipants(ctx, info.Members, source, nil, time.Time{}) + err := portal.syncParticipants(ctx, info.Members, source, nil, time.Time{}) if err != nil { zerolog.Ctx(ctx).Err(err).Msg("Failed to sync room members") } @@ -2903,7 +2903,7 @@ func (portal *Portal) createMatrixRoomInLoop(ctx context.Context, source *UserLo }, Users: map[id.UserID]int{}, } - initialMembers, extraFunctionalMembers, err := portal.GetInitialMemberList(ctx, info.Members, source, powerLevels) + initialMembers, extraFunctionalMembers, err := portal.getInitialMemberList(ctx, info.Members, source, powerLevels) if err != nil { log.Err(err).Msg("Failed to process participant list for portal creation") return err @@ -3026,7 +3026,7 @@ func (portal *Portal) createMatrixRoomInLoop(ctx context.Context, source *UserLo } } } else { - err = portal.SyncParticipants(ctx, info.Members, source, nil, time.Time{}) + err = portal.syncParticipants(ctx, info.Members, source, nil, time.Time{}) if err != nil { log.Err(err).Msg("Failed to sync participants after room creation") } diff --git a/bridgev2/portalinternal.go b/bridgev2/portalinternal.go new file mode 100644 index 00000000..c261bd2d --- /dev/null +++ b/bridgev2/portalinternal.go @@ -0,0 +1,266 @@ +// GENERATED BY portalinternal_generate.go; DO NOT EDIT + +//go:generate go run portalinternal_generate.go +//go:generate goimports -w portalinternal.go + +package bridgev2 + +import ( + "context" + "time" + + "github.com/rs/zerolog" + + "maunium.net/go/mautrix" + "maunium.net/go/mautrix/bridgev2/database" + "maunium.net/go/mautrix/bridgev2/networkid" + "maunium.net/go/mautrix/event" + "maunium.net/go/mautrix/id" +) + +type PortalInternals Portal + +// Deprecated: portal internals should be used carefully and only when necessary. +func (portal *Portal) Internal() *PortalInternals { + return (*PortalInternals)(portal) +} + +func (portal *PortalInternals) UpdateLogger() { + (*Portal)(portal).updateLogger() +} + +func (portal *PortalInternals) QueueEvent(ctx context.Context, evt portalEvent) { + (*Portal)(portal).queueEvent(ctx, evt) +} + +func (portal *PortalInternals) EventLoop() { + (*Portal)(portal).eventLoop() +} + +func (portal *PortalInternals) HandleCreateEvent(evt *portalCreateEvent) { + (*Portal)(portal).handleCreateEvent(evt) +} + +func (portal *PortalInternals) SendSuccessStatus(ctx context.Context, evt *event.Event) { + (*Portal)(portal).sendSuccessStatus(ctx, evt) +} + +func (portal *PortalInternals) SendErrorStatus(ctx context.Context, evt *event.Event, err error) { + (*Portal)(portal).sendErrorStatus(ctx, evt, err) +} + +func (portal *PortalInternals) HandleMatrixEvent(sender *User, evt *event.Event) { + (*Portal)(portal).handleMatrixEvent(sender, evt) +} + +func (portal *PortalInternals) HandleMatrixReceipts(ctx context.Context, evt *event.Event) { + (*Portal)(portal).handleMatrixReceipts(ctx, evt) +} + +func (portal *PortalInternals) HandleMatrixReadReceipt(ctx context.Context, user *User, eventID id.EventID, receipt event.ReadReceipt) { + (*Portal)(portal).handleMatrixReadReceipt(ctx, user, eventID, receipt) +} + +func (portal *PortalInternals) HandleMatrixTyping(ctx context.Context, evt *event.Event) { + (*Portal)(portal).handleMatrixTyping(ctx, evt) +} + +func (portal *PortalInternals) SendTypings(ctx context.Context, userIDs []id.UserID, typing bool) { + (*Portal)(portal).sendTypings(ctx, userIDs, typing) +} + +func (portal *PortalInternals) PeriodicTypingUpdater() { + (*Portal)(portal).periodicTypingUpdater() +} + +func (portal *PortalInternals) CheckMessageContentCaps(ctx context.Context, caps *NetworkRoomCapabilities, content *event.MessageEventContent, evt *event.Event) bool { + return (*Portal)(portal).checkMessageContentCaps(ctx, caps, content, evt) +} + +func (portal *PortalInternals) HandleMatrixMessage(ctx context.Context, sender *UserLogin, origSender *OrigSender, evt *event.Event) { + (*Portal)(portal).handleMatrixMessage(ctx, sender, origSender, evt) +} + +func (portal *PortalInternals) HandleMatrixEdit(ctx context.Context, sender *UserLogin, origSender *OrigSender, evt *event.Event, content *event.MessageEventContent, caps *NetworkRoomCapabilities) { + (*Portal)(portal).handleMatrixEdit(ctx, sender, origSender, evt, content, caps) +} + +func (portal *PortalInternals) HandleMatrixReaction(ctx context.Context, sender *UserLogin, evt *event.Event) { + (*Portal)(portal).handleMatrixReaction(ctx, sender, evt) +} + +func (portal *PortalInternals) HandleMatrixRedaction(ctx context.Context, sender *UserLogin, origSender *OrigSender, evt *event.Event) { + (*Portal)(portal).handleMatrixRedaction(ctx, sender, origSender, evt) +} + +func (portal *PortalInternals) HandleRemoteEvent(source *UserLogin, evt RemoteEvent) { + (*Portal)(portal).handleRemoteEvent(source, evt) +} + +func (portal *PortalInternals) GetIntentAndUserMXIDFor(ctx context.Context, sender EventSender, source *UserLogin, otherLogins []*UserLogin, evtType RemoteEventType) (intent MatrixAPI, extraUserID id.UserID) { + return (*Portal)(portal).getIntentAndUserMXIDFor(ctx, sender, source, otherLogins, evtType) +} + +func (portal *PortalInternals) GetRelationMeta(ctx context.Context, currentMsg networkid.MessageID, replyToPtr *networkid.MessageOptionalPartID, threadRootPtr *networkid.MessageID, isBatchSend bool) (replyTo, threadRoot, prevThreadEvent *database.Message) { + return (*Portal)(portal).getRelationMeta(ctx, currentMsg, replyToPtr, threadRootPtr, isBatchSend) +} + +func (portal *PortalInternals) ApplyRelationMeta(content *event.MessageEventContent, replyTo, threadRoot, prevThreadEvent *database.Message) { + (*Portal)(portal).applyRelationMeta(content, replyTo, threadRoot, prevThreadEvent) +} + +func (portal *PortalInternals) SendConvertedMessage(ctx context.Context, id networkid.MessageID, intent MatrixAPI, senderID networkid.UserID, converted *ConvertedMessage, ts time.Time, logContext func(*zerolog.Event) *zerolog.Event) []*database.Message { + return (*Portal)(portal).sendConvertedMessage(ctx, id, intent, senderID, converted, ts, logContext) +} + +func (portal *PortalInternals) CheckPendingMessage(ctx context.Context, evt RemoteMessage) (bool, *database.Message) { + return (*Portal)(portal).checkPendingMessage(ctx, evt) +} + +func (portal *PortalInternals) HandleRemoteUpsert(ctx context.Context, source *UserLogin, evt RemoteMessageUpsert, existing []*database.Message) bool { + return (*Portal)(portal).handleRemoteUpsert(ctx, source, evt, existing) +} + +func (portal *PortalInternals) HandleRemoteMessage(ctx context.Context, source *UserLogin, evt RemoteMessage) { + (*Portal)(portal).handleRemoteMessage(ctx, source, evt) +} + +func (portal *PortalInternals) SendRemoteErrorNotice(ctx context.Context, intent MatrixAPI, err error, ts time.Time, evtTypeName string) { + (*Portal)(portal).sendRemoteErrorNotice(ctx, intent, err, ts, evtTypeName) +} + +func (portal *PortalInternals) HandleRemoteEdit(ctx context.Context, source *UserLogin, evt RemoteEdit) { + (*Portal)(portal).handleRemoteEdit(ctx, source, evt) +} + +func (portal *PortalInternals) SendConvertedEdit(ctx context.Context, targetID networkid.MessageID, senderID networkid.UserID, converted *ConvertedEdit, intent MatrixAPI, ts time.Time) { + (*Portal)(portal).sendConvertedEdit(ctx, targetID, senderID, converted, intent, ts) +} + +func (portal *PortalInternals) GetTargetMessagePart(ctx context.Context, evt RemoteEventWithTargetMessage) (*database.Message, error) { + return (*Portal)(portal).getTargetMessagePart(ctx, evt) +} + +func (portal *PortalInternals) GetTargetReaction(ctx context.Context, evt RemoteReactionRemove) (*database.Reaction, error) { + return (*Portal)(portal).getTargetReaction(ctx, evt) +} + +func (portal *PortalInternals) HandleRemoteReactionSync(ctx context.Context, source *UserLogin, evt RemoteReactionSync) { + (*Portal)(portal).handleRemoteReactionSync(ctx, source, evt) +} + +func (portal *PortalInternals) HandleRemoteReaction(ctx context.Context, source *UserLogin, evt RemoteReaction) { + (*Portal)(portal).handleRemoteReaction(ctx, source, evt) +} + +func (portal *PortalInternals) SendConvertedReaction(ctx context.Context, senderID networkid.UserID, intent MatrixAPI, targetMessage *database.Message, emojiID networkid.EmojiID, emoji string, ts time.Time, dbMetadata any, extraContent map[string]any, logContext func(*zerolog.Event) *zerolog.Event) { + (*Portal)(portal).sendConvertedReaction(ctx, senderID, intent, targetMessage, emojiID, emoji, ts, dbMetadata, extraContent, logContext) +} + +func (portal *PortalInternals) GetIntentForMXID(ctx context.Context, userID id.UserID) (MatrixAPI, error) { + return (*Portal)(portal).getIntentForMXID(ctx, userID) +} + +func (portal *PortalInternals) HandleRemoteReactionRemove(ctx context.Context, source *UserLogin, evt RemoteReactionRemove) { + (*Portal)(portal).handleRemoteReactionRemove(ctx, source, evt) +} + +func (portal *PortalInternals) HandleRemoteMessageRemove(ctx context.Context, source *UserLogin, evt RemoteMessageRemove) { + (*Portal)(portal).handleRemoteMessageRemove(ctx, source, evt) +} + +func (portal *PortalInternals) RedactMessageParts(ctx context.Context, parts []*database.Message, intent MatrixAPI, ts time.Time) { + (*Portal)(portal).redactMessageParts(ctx, parts, intent, ts) +} + +func (portal *PortalInternals) HandleRemoteReadReceipt(ctx context.Context, source *UserLogin, evt RemoteReceipt) { + (*Portal)(portal).handleRemoteReadReceipt(ctx, source, evt) +} + +func (portal *PortalInternals) HandleRemoteMarkUnread(ctx context.Context, source *UserLogin, evt RemoteMarkUnread) { + (*Portal)(portal).handleRemoteMarkUnread(ctx, source, evt) +} + +func (portal *PortalInternals) HandleRemoteDeliveryReceipt(ctx context.Context, source *UserLogin, evt RemoteReceipt) { + (*Portal)(portal).handleRemoteDeliveryReceipt(ctx, source, evt) +} + +func (portal *PortalInternals) HandleRemoteTyping(ctx context.Context, source *UserLogin, evt RemoteTyping) { + (*Portal)(portal).handleRemoteTyping(ctx, source, evt) +} + +func (portal *PortalInternals) HandleRemoteChatInfoChange(ctx context.Context, source *UserLogin, evt RemoteChatInfoChange) { + (*Portal)(portal).handleRemoteChatInfoChange(ctx, source, evt) +} + +func (portal *PortalInternals) HandleRemoteChatResync(ctx context.Context, source *UserLogin, evt RemoteChatResync) { + (*Portal)(portal).handleRemoteChatResync(ctx, source, evt) +} + +func (portal *PortalInternals) HandleRemoteChatDelete(ctx context.Context, source *UserLogin, evt RemoteChatDelete) { + (*Portal)(portal).handleRemoteChatDelete(ctx, source, evt) +} + +func (portal *PortalInternals) HandleRemoteBackfill(ctx context.Context, source *UserLogin, backfill RemoteBackfill) { + (*Portal)(portal).handleRemoteBackfill(ctx, source, backfill) +} + +func (portal *PortalInternals) UpdateName(ctx context.Context, name string, sender MatrixAPI, ts time.Time) bool { + return (*Portal)(portal).updateName(ctx, name, sender, ts) +} + +func (portal *PortalInternals) UpdateTopic(ctx context.Context, topic string, sender MatrixAPI, ts time.Time) bool { + return (*Portal)(portal).updateTopic(ctx, topic, sender, ts) +} + +func (portal *PortalInternals) UpdateAvatar(ctx context.Context, avatar *Avatar, sender MatrixAPI, ts time.Time) bool { + return (*Portal)(portal).updateAvatar(ctx, avatar, sender, ts) +} + +func (portal *PortalInternals) GetBridgeInfo() (string, event.BridgeEventContent) { + return (*Portal)(portal).getBridgeInfo() +} + +func (portal *PortalInternals) SendStateWithIntentOrBot(ctx context.Context, sender MatrixAPI, eventType event.Type, stateKey string, content *event.Content, ts time.Time) (resp *mautrix.RespSendEvent, err error) { + return (*Portal)(portal).sendStateWithIntentOrBot(ctx, sender, eventType, stateKey, content, ts) +} + +func (portal *PortalInternals) SendRoomMeta(ctx context.Context, sender MatrixAPI, ts time.Time, eventType event.Type, stateKey string, content any) bool { + return (*Portal)(portal).sendRoomMeta(ctx, sender, ts, eventType, stateKey, content) +} + +func (portal *PortalInternals) GetInitialMemberList(ctx context.Context, members *ChatMemberList, source *UserLogin, pl *event.PowerLevelsEventContent) (invite, functional []id.UserID, err error) { + return (*Portal)(portal).getInitialMemberList(ctx, members, source, pl) +} + +func (portal *PortalInternals) UpdateOtherUser(ctx context.Context, members *ChatMemberList) (changed bool) { + return (*Portal)(portal).updateOtherUser(ctx, members) +} + +func (portal *PortalInternals) SyncParticipants(ctx context.Context, members *ChatMemberList, source *UserLogin, sender MatrixAPI, ts time.Time) error { + return (*Portal)(portal).syncParticipants(ctx, members, source, sender, ts) +} + +func (portal *PortalInternals) UpdateUserLocalInfo(ctx context.Context, info *UserLocalPortalInfo, source *UserLogin) { + (*Portal)(portal).updateUserLocalInfo(ctx, info, source) +} + +func (portal *PortalInternals) UpdateParent(ctx context.Context, newParent networkid.PortalID, source *UserLogin) bool { + return (*Portal)(portal).updateParent(ctx, newParent, source) +} + +func (portal *PortalInternals) LockedUpdateInfoFromGhost(ctx context.Context, ghost *Ghost) { + (*Portal)(portal).lockedUpdateInfoFromGhost(ctx, ghost) +} + +func (portal *PortalInternals) CreateMatrixRoomInLoop(ctx context.Context, source *UserLogin, info *ChatInfo) error { + return (*Portal)(portal).createMatrixRoomInLoop(ctx, source, info) +} + +func (portal *PortalInternals) UnlockedDelete(ctx context.Context) error { + return (*Portal)(portal).unlockedDelete(ctx) +} + +func (portal *PortalInternals) UnlockedDeleteCache() { + (*Portal)(portal).unlockedDeleteCache() +} diff --git a/bridgev2/portalinternal_generate.go b/bridgev2/portalinternal_generate.go new file mode 100644 index 00000000..8fd9e917 --- /dev/null +++ b/bridgev2/portalinternal_generate.go @@ -0,0 +1,160 @@ +// Copyright (c) 2024 Tulir Asokan +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +//go:build ignore + +package main + +import ( + "fmt" + "go/ast" + "go/parser" + "go/token" + "os" + "strings" + + "go.mau.fi/util/exerrors" +) + +const header = `// GENERATED BY portalinternal_generate.go; DO NOT EDIT + +//go:generate go run portalinternal_generate.go +//go:generate goimports -w portalinternal.go + +package bridgev2 + +` +const postImportHeader = ` +type PortalInternals Portal + +// Deprecated: portal internals should be used carefully and only when necessary. +func (portal *Portal) Internal() *PortalInternals { + return (*PortalInternals)(portal) +} +` + +func getTypeName(expr ast.Expr) string { + switch e := expr.(type) { + case *ast.Ident: + return e.Name + case *ast.StarExpr: + return "*" + getTypeName(e.X) + case *ast.ArrayType: + return "[]" + getTypeName(e.Elt) + case *ast.MapType: + return fmt.Sprintf("map[%s]%s", getTypeName(e.Key), getTypeName(e.Value)) + case *ast.ChanType: + return fmt.Sprintf("chan %s", getTypeName(e.Value)) + case *ast.FuncType: + var params []string + for _, param := range e.Params.List { + params = append(params, getTypeName(param.Type)) + } + var results []string + if e.Results != nil { + for _, result := range e.Results.List { + results = append(results, getTypeName(result.Type)) + } + } + return fmt.Sprintf("func(%s) %s", strings.Join(params, ", "), strings.Join(results, ", ")) + case *ast.SelectorExpr: + return fmt.Sprintf("%s.%s", getTypeName(e.X), e.Sel.Name) + default: + panic(fmt.Errorf("unknown type %T", e)) + } +} + +func main() { + fset := token.NewFileSet() + f := exerrors.Must(parser.ParseFile(fset, "portal.go", nil, parser.SkipObjectResolution)) + file := exerrors.Must(os.OpenFile("portalinternal.go", os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0644)) + write := func(str string) { + exerrors.Must(file.WriteString(str)) + } + writef := func(format string, args ...any) { + exerrors.Must(fmt.Fprintf(file, format, args...)) + } + write(header) + write("import (\n") + for _, i := range f.Imports { + write("\t") + if i.Name != nil { + writef("%s ", i.Name.Name) + } + writef("%s\n", i.Path.Value) + } + write(")\n") + write(postImportHeader) + ast.Inspect(f, func(node ast.Node) (retVal bool) { + retVal = true + funcDecl, ok := node.(*ast.FuncDecl) + if !ok || funcDecl.Name.IsExported() { + return + } + if funcDecl.Recv == nil || len(funcDecl.Recv.List) == 0 || len(funcDecl.Recv.List[0].Names) == 0 || + funcDecl.Recv.List[0].Names[0].Name != "portal" { + return + } + writef("\nfunc (portal *PortalInternals) %s%s(", strings.ToUpper(funcDecl.Name.Name[0:1]), funcDecl.Name.Name[1:]) + for i, param := range funcDecl.Type.Params.List { + if i != 0 { + write(", ") + } + for j, name := range param.Names { + if j != 0 { + write(", ") + } + write(name.Name) + } + if len(param.Names) > 0 { + write(" ") + } + write(getTypeName(param.Type)) + } + write(") ") + if funcDecl.Type.Results != nil && len(funcDecl.Type.Results.List) > 0 { + needsParentheses := len(funcDecl.Type.Results.List) > 1 || len(funcDecl.Type.Results.List[0].Names) > 0 + if needsParentheses { + write("(") + } + for i, result := range funcDecl.Type.Results.List { + if i != 0 { + write(", ") + } + for j, name := range result.Names { + if j != 0 { + write(", ") + } + write(name.Name) + } + if len(result.Names) > 0 { + write(" ") + } + write(getTypeName(result.Type)) + } + if needsParentheses { + write(")") + } + write(" ") + } + write("{\n\t") + if funcDecl.Type.Results != nil { + write("return ") + } + writef("(*Portal)(portal).%s(", funcDecl.Name.Name) + for i, param := range funcDecl.Type.Params.List { + for j, name := range param.Names { + if i != 0 || j != 0 { + write(", ") + } + write(name.Name) + } + } + write(")\n}\n") + return + }) + exerrors.PanicIfNotNil(file.Close()) +}