github.com/diamondburned/arikawa/v2@v2.1.0/bot/extras/infer/infer.go (about) 1 // Package infer implements reflect functions that package bot uses. 2 // 3 // Functions in this package may run recursively forever. This shouldn't happen 4 // with Arikawa's structures, but use these functions with care. 5 package infer 6 7 import ( 8 "reflect" 9 "strings" 10 11 "github.com/diamondburned/arikawa/v2/discord" 12 ) 13 14 // ChannelID looks for fields with name ChannelID, Channel, or in some special 15 // cases, ID. 16 func ChannelID(event interface{}) discord.ChannelID { 17 return discord.ChannelID(reflectID(reflect.ValueOf(event), "Channel")) 18 } 19 20 // GuildID looks for fields with name GuildID, Guild, or in some special cases, 21 // ID. 22 func GuildID(event interface{}) discord.GuildID { 23 return discord.GuildID(reflectID(reflect.ValueOf(event), "Guild")) 24 } 25 26 // UserID looks for fields with name UserID, User, or in some special cases, ID. 27 func UserID(event interface{}) discord.UserID { 28 // This may have a very fatal bug of accidentally mistaking another User's 29 // ID. It also probably wouldn't work with things like RecipientID. 30 return discord.UserID(reflectID(reflect.ValueOf(event), "User")) 31 } 32 33 func reflectID(v reflect.Value, thing string) discord.Snowflake { 34 if !v.IsValid() { 35 return 0 36 } 37 38 t := v.Type() 39 40 if t.Kind() == reflect.Ptr { 41 v = v.Elem() 42 43 // Recheck after dereferring 44 if !v.IsValid() { 45 return 0 46 } 47 48 t = v.Type() 49 } 50 51 if t.Kind() != reflect.Struct { 52 return 0 53 } 54 55 numFields := t.NumField() 56 57 for i := 0; i < numFields; i++ { 58 field := t.Field(i) 59 fType := field.Type 60 61 if fType.Kind() == reflect.Ptr { 62 fType = fType.Elem() 63 } 64 65 switch fType.Kind() { 66 case reflect.Struct: 67 if chID := reflectID(v.Field(i), thing); chID.IsValid() { 68 return chID 69 } 70 case reflect.Uint64: 71 switch { 72 case false, 73 // Contains works with "LastMessageID" and such. 74 strings.Contains(field.Name, thing+"ID"), 75 // Special case where the struct name has Channel in it. 76 field.Name == "ID" && strings.Contains(t.Name(), thing): 77 78 return discord.Snowflake(v.Field(i).Uint()) 79 } 80 } 81 } 82 83 return 0 84 } 85 86 /* 87 var reflectCache sync.Map 88 89 type cacheKey struct { 90 t reflect.Type 91 f string 92 } 93 94 func getID(v reflect.Value, thing string) discord.Snowflake { 95 if !v.IsValid() { 96 return 0 97 } 98 99 t := v.Type() 100 101 if t.Kind() == reflect.Ptr { 102 v = v.Elem() 103 104 // Recheck after dereferring 105 if !v.IsValid() { 106 return 0 107 } 108 109 t = v.Type() 110 } 111 112 if t.Kind() != reflect.Struct { 113 return 0 114 } 115 116 return reflectID(thing, v, t) 117 } 118 119 type reflector struct { 120 steps []step 121 thing string 122 thingID string 123 } 124 125 type step struct { 126 field int 127 ptr bool 128 rec []step 129 } 130 131 func reflectID(thing string, v reflect.Value, t reflect.Type) discord.Snowflake { 132 r := &reflector{thing: thing} 133 134 // copy original type 135 key := r.thing + t.String() 136 137 // check the cache 138 if instructions, ok := reflectCache.Load(key); ok { 139 if instructions == nil { 140 return 0 141 } 142 return applyInstructions(v, instructions.([]step)) 143 } 144 145 r.thingID = r.thing + "ID" 146 r.steps = make([]step, 0, 1) 147 id := r._id(v, t) 148 149 if r.steps != nil { 150 reflectCache.Store(key, r.instructions()) 151 } 152 153 return id 154 } 155 156 func applyInstructions(v reflect.Value, instructions []step) discord.Snowflake { 157 // Use a type here to detect recursion: 158 // var originalT = v.Type() 159 var laststep reflect.Value 160 161 log.Println(v.Type(), instructions) 162 163 for i, step := range instructions { 164 if !v.IsValid() { 165 return 0 166 } 167 if i > 0 && step.ptr { 168 v = v.Elem() 169 } 170 if !v.IsValid() { 171 // is this the bottom of the instructions? 172 if i == len(instructions)-1 && step.rec != nil { 173 for _, ins := range step.rec { 174 var value = laststep.Field(ins.field) 175 if ins.ptr { 176 value = value.Elem() 177 } 178 if id := applyInstructions(value, instructions); id.IsValid() { 179 return id 180 } 181 } 182 } 183 return 0 184 } 185 laststep = v 186 v = laststep.Field(step.field) 187 } 188 return discord.Snowflake(v.Int()) 189 } 190 191 func (r *reflector) instructions() []step { 192 if len(r.steps) == 0 { 193 return nil 194 } 195 var instructions = make([]step, len(r.steps)) 196 for i := 0; i < len(instructions); i++ { 197 instructions[i] = r.steps[len(r.steps)-i-1] 198 } 199 // instructions := r.steps 200 return instructions 201 } 202 203 func (r *reflector) step(s step) { 204 r.steps = append(r.steps, s) 205 } 206 207 func (r *reflector) _id(v reflect.Value, t reflect.Type) (chID discord.Snowflake) { 208 numFields := t.NumField() 209 210 var ptr bool 211 var ins = step{field: -1} 212 213 for i := 0; i < numFields; i++ { 214 field := t.Field(i) 215 fType := field.Type 216 value := v.Field(i) 217 ptr = false 218 219 if fType.Kind() == reflect.Ptr { 220 fType = fType.Elem() 221 value = value.Elem() 222 ptr = true 223 } 224 225 // does laststep have the same field type? 226 if fType == t { 227 ins.rec = append(ins.rec, step{field: i, ptr: ptr}) 228 } 229 230 if !value.IsValid() { 231 continue 232 } 233 234 // If we've already found the field: 235 if ins.field > 0 { 236 continue 237 } 238 239 switch fType.Kind() { 240 case reflect.Struct: 241 if chID = r._id(value, fType); chID.IsValid() { 242 ins.field = i 243 ins.ptr = ptr 244 } 245 case reflect.Int64: 246 switch { 247 case false, 248 // Contains works with "LastMessageID" and such. 249 strings.Contains(field.Name, r.thingID), 250 // Special case where the struct name has Channel in it. 251 field.Name == "ID" && strings.Contains(t.Name(), r.thing): 252 253 ins.field = i 254 ins.ptr = ptr 255 256 chID = discord.Snowflake(value.Int()) 257 } 258 } 259 } 260 261 // If we've found the field: 262 r.step(ins) 263 264 return 265 } 266 */